feat: NIP-43
This commit is contained in:
@@ -2,15 +2,17 @@ import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
|
||||
import { useFetchRelayInfo } from '@/hooks'
|
||||
import { checkNip43Support } from '@/lib/relay'
|
||||
import { normalizeHttpUrl } from '@/lib/url'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { Check, Copy, GitBranch, Link, Mail, SquareCode } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import PostEditor from '../PostEditor'
|
||||
import RelayIcon from '../RelayIcon'
|
||||
import RelayMembershipControl from '../RelayMembershipControl'
|
||||
import SaveRelayDropdownMenu from '../SaveRelayDropdownMenu'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
import Username from '../Username'
|
||||
@@ -21,6 +23,9 @@ export default function RelayInfo({ url, className }: { url: string; className?:
|
||||
const { checkLogin } = useNostr()
|
||||
const { relayInfo, isFetching } = useFetchRelayInfo(url)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [isMember, setIsMember] = useState(false)
|
||||
const supportsNip43 = useMemo(() => checkNip43Support(relayInfo), [relayInfo])
|
||||
const shouldShowPostButton = useMemo(() => !supportsNip43 || isMember, [supportsNip43, isMember])
|
||||
|
||||
if (isFetching || !relayInfo) {
|
||||
return null
|
||||
@@ -105,14 +110,19 @@ export default function RelayInfo({ url, className }: { url: string; className?:
|
||||
</div>
|
||||
<ScrollBar orientation="horizontal" />
|
||||
</ScrollArea>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="w-full"
|
||||
onClick={() => checkLogin(() => setOpen(true))}
|
||||
>
|
||||
{t('Share something on this Relay')}
|
||||
</Button>
|
||||
<PostEditor open={open} setOpen={setOpen} openFrom={[relayInfo.url]} />
|
||||
<RelayMembershipControl relayInfo={relayInfo} onMembershipStatusChange={setIsMember} />
|
||||
{shouldShowPostButton && (
|
||||
<>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="w-full"
|
||||
onClick={() => checkLogin(() => setOpen(true))}
|
||||
>
|
||||
{t('Share something on this Relay')}
|
||||
</Button>
|
||||
<PostEditor open={open} setOpen={setOpen} openFrom={[relayInfo.url]} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<RelayReviewsPreview relayUrl={url} />
|
||||
</div>
|
||||
|
||||
134
src/components/RelayMembershipControl/InviteCodeDialog.tsx
Normal file
134
src/components/RelayMembershipControl/InviteCodeDialog.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog'
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerHeader,
|
||||
DrawerTitle
|
||||
} from '@/components/ui/drawer'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import relayMembershipService from '@/services/relay-membership.service'
|
||||
import { TRelayInfo } from '@/types'
|
||||
import { Check, Copy } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export default function InviteCodeDialog({
|
||||
relayInfo,
|
||||
showInviteCodeDialog,
|
||||
setShowInviteCodeDialog
|
||||
}: {
|
||||
relayInfo: TRelayInfo
|
||||
showInviteCodeDialog: boolean
|
||||
setShowInviteCodeDialog: (open: boolean) => void
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
const [isFetching, setIsFetching] = useState(false)
|
||||
const [inviteCode, setInviteCode] = useState('')
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!showInviteCodeDialog) {
|
||||
setInviteCode('')
|
||||
return
|
||||
}
|
||||
|
||||
const getInviteCode = async () => {
|
||||
setIsFetching(true)
|
||||
try {
|
||||
if (relayInfo.pubkey) {
|
||||
const code = await relayMembershipService.requestInviteCode(
|
||||
relayInfo.url,
|
||||
relayInfo.pubkey
|
||||
)
|
||||
if (code) {
|
||||
setInviteCode(code)
|
||||
} else {
|
||||
toast.error(t('Failed to get invite code from relay'))
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(error.message || t('Failed to get invite code'))
|
||||
} finally {
|
||||
setIsFetching(false)
|
||||
}
|
||||
}
|
||||
getInviteCode()
|
||||
}, [showInviteCodeDialog])
|
||||
|
||||
const handleCopyInviteCode = () => {
|
||||
if (!inviteCode) return
|
||||
|
||||
navigator.clipboard.writeText(inviteCode)
|
||||
toast.success(t('Invite code copied to clipboard'))
|
||||
setCopied(true)
|
||||
|
||||
setTimeout(() => {
|
||||
setCopied(false)
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
const content = isFetching ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="text-muted-foreground">{t('Loading...')}</div>
|
||||
</div>
|
||||
) : inviteCode ? (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="fetched-invite-code">{t('Invite Code')}</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input id="fetched-invite-code" value={inviteCode} readOnly className="font-mono" />
|
||||
<Button onClick={handleCopyInviteCode} variant="outline">
|
||||
{copied ? <Check /> : <Copy />}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('This invite code can be used by others to join the relay.')}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
{t('No invite code available from this relay.')}
|
||||
</div>
|
||||
)
|
||||
|
||||
if (isSmallScreen) {
|
||||
return (
|
||||
<Drawer open={showInviteCodeDialog} onOpenChange={setShowInviteCodeDialog}>
|
||||
<DrawerContent>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>{t('Get Invite Code')}</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
{t('Share this invite code with others to invite them to join this relay.')}
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className="p-4">{content}</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={showInviteCodeDialog} onOpenChange={setShowInviteCodeDialog}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('Get Invite Code')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t('Share this invite code with others to invite them to join this relay.')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{content}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
141
src/components/RelayMembershipControl/JoinDialog.tsx
Normal file
141
src/components/RelayMembershipControl/JoinDialog.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog'
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle
|
||||
} from '@/components/ui/drawer'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { createJoinDraftEvent } from '@/lib/draft-event'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import relayMembershipService from '@/services/relay-membership.service'
|
||||
import { TRelayInfo } from '@/types'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export default function JoinDialog({
|
||||
relayInfo,
|
||||
showJoinDialog,
|
||||
setShowJoinDialog,
|
||||
onMembershipStatusChange
|
||||
}: {
|
||||
relayInfo: TRelayInfo
|
||||
showJoinDialog: boolean
|
||||
setShowJoinDialog: (open: boolean) => void
|
||||
onMembershipStatusChange: (status: boolean) => void
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
const { publish } = useNostr()
|
||||
const [inviteCode, setInviteCode] = useState('')
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const handleJoinSubmit = async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const draftEvent = createJoinDraftEvent(inviteCode)
|
||||
const joinRequestEvent = await publish(draftEvent, {
|
||||
specifiedRelayUrls: [relayInfo.url]
|
||||
})
|
||||
toast.success(t('Join request sent successfully'))
|
||||
await relayMembershipService.addNewMember(relayInfo.url, joinRequestEvent.pubkey)
|
||||
onMembershipStatusChange(true)
|
||||
setInviteCode('')
|
||||
setShowJoinDialog(false)
|
||||
} catch (error) {
|
||||
const errors = error instanceof AggregateError ? error.errors : [error]
|
||||
errors.forEach((err) => {
|
||||
toast.error(
|
||||
`${t('Failed to send join request')}: ${err instanceof Error ? err.message : String(err)}`,
|
||||
{ duration: 10_000 }
|
||||
)
|
||||
console.error(err)
|
||||
})
|
||||
return
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const content = (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="invite-code">{t('Invite Code')}</Label>
|
||||
<Input
|
||||
id="invite-code"
|
||||
value={inviteCode}
|
||||
onChange={(e) => setInviteCode(e.target.value)}
|
||||
placeholder={t('Enter invite code')}
|
||||
required
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('You can get an invite code from a relay member.')}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
if (isSmallScreen) {
|
||||
return (
|
||||
<Drawer open={showJoinDialog} onOpenChange={setShowJoinDialog}>
|
||||
<DrawerContent>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>{t('Request to Join Relay')}</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
{t('Enter the invite code you received from a relay member.')}
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className="p-4">{content}</div>
|
||||
<DrawerFooter>
|
||||
<Button onClick={handleJoinSubmit} disabled={isLoading || !inviteCode.trim()}>
|
||||
{isLoading ? t('Sending...') : t('Send Request')}
|
||||
</Button>
|
||||
<DrawerClose asChild>
|
||||
<Button variant="outline">{t('Cancel')}</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={showJoinDialog} onOpenChange={setShowJoinDialog}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('Request to Join Relay')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t('Enter the invite code you received from a relay member.')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{content}
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setShowJoinDialog(false)
|
||||
setInviteCode('')
|
||||
}}
|
||||
>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
<Button onClick={handleJoinSubmit} disabled={isLoading || !inviteCode.trim()}>
|
||||
{isLoading ? t('Sending...') : t('Send Request')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
166
src/components/RelayMembershipControl/index.tsx
Normal file
166
src/components/RelayMembershipControl/index.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { createJoinDraftEvent, createLeaveDraftEvent } from '@/lib/draft-event'
|
||||
import { checkNip43Support } from '@/lib/relay'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import relayMembershipService from '@/services/relay-membership.service'
|
||||
import { TRelayInfo } from '@/types'
|
||||
import { LogIn, LogOut, Mail } from 'lucide-react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import InviteCodeDialog from './InviteCodeDialog'
|
||||
import JoinDialog from './JoinDialog'
|
||||
|
||||
interface RelayMembershipControlProps {
|
||||
relayInfo: TRelayInfo
|
||||
onMembershipStatusChange?: (status: boolean) => void
|
||||
}
|
||||
|
||||
export default function RelayMembershipControl({
|
||||
relayInfo,
|
||||
onMembershipStatusChange
|
||||
}: RelayMembershipControlProps) {
|
||||
const { t } = useTranslation()
|
||||
const { pubkey, checkLogin, publish } = useNostr()
|
||||
const [isMember, setIsMember] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [isChecking, setIsChecking] = useState(false)
|
||||
const [showJoinDialog, setShowJoinDialog] = useState(false)
|
||||
const [showInviteCodeDialog, setShowInviteCodeDialog] = useState(false)
|
||||
const supportsNip43 = useMemo(() => checkNip43Support(relayInfo), [relayInfo])
|
||||
|
||||
useEffect(() => {
|
||||
if (!supportsNip43 || !pubkey) {
|
||||
setIsMember(false)
|
||||
return
|
||||
}
|
||||
|
||||
const checkMembership = async () => {
|
||||
try {
|
||||
setIsChecking(true)
|
||||
const status = await relayMembershipService.checkMembership(
|
||||
relayInfo.url,
|
||||
pubkey,
|
||||
relayInfo.pubkey
|
||||
)
|
||||
setIsMember(status)
|
||||
} finally {
|
||||
setIsChecking(false)
|
||||
}
|
||||
}
|
||||
|
||||
checkMembership()
|
||||
}, [relayInfo.url, relayInfo.pubkey, pubkey, supportsNip43])
|
||||
|
||||
useEffect(() => {
|
||||
if (onMembershipStatusChange) {
|
||||
onMembershipStatusChange(isMember)
|
||||
}
|
||||
}, [isMember, onMembershipStatusChange])
|
||||
|
||||
if (!supportsNip43 || isChecking) {
|
||||
return null
|
||||
}
|
||||
|
||||
const submitJoinRequest = async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const draftEvent = createJoinDraftEvent('')
|
||||
const joinRequestEvent = await publish(draftEvent, {
|
||||
specifiedRelayUrls: [relayInfo.url]
|
||||
})
|
||||
toast.success(t('Join request sent successfully'))
|
||||
await relayMembershipService.addNewMember(relayInfo.url, joinRequestEvent.pubkey)
|
||||
onMembershipStatusChange?.(true)
|
||||
} catch {
|
||||
setShowJoinDialog(true)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleGetInviteCodeClick = () => {
|
||||
setShowInviteCodeDialog(true)
|
||||
}
|
||||
|
||||
const handleLeaveClick = async () => {
|
||||
if (!confirm(t('Are you sure you want to leave this relay?'))) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const draftEvent = createLeaveDraftEvent()
|
||||
const leaveRequestEvent = await publish(draftEvent, {
|
||||
specifiedRelayUrls: [relayInfo.url]
|
||||
})
|
||||
toast.success(t('Leave request sent successfully'))
|
||||
await relayMembershipService.removeMember(relayInfo.url, leaveRequestEvent.pubkey)
|
||||
setIsMember(false)
|
||||
} catch (error: any) {
|
||||
const errors = error instanceof AggregateError ? error.errors : [error]
|
||||
errors.forEach((err) => {
|
||||
toast.error(
|
||||
`${t('Failed to send leave request')}: ${err instanceof Error ? err.message : String(err)}`,
|
||||
{ duration: 10_000 }
|
||||
)
|
||||
console.error(err)
|
||||
})
|
||||
return
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMember ? (
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="w-full"
|
||||
onClick={handleGetInviteCodeClick}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Mail className="w-4 h-4 mr-2" />
|
||||
{t('Get Invite Code')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={handleLeaveClick}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<LogOut className="w-4 h-4 mr-2" />
|
||||
{t('Leave')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
variant="default"
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
checkLogin(() => submitJoinRequest())
|
||||
}}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<LogIn className="w-4 h-4 mr-2" />
|
||||
{t('Request to Join Relay')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<JoinDialog
|
||||
relayInfo={relayInfo}
|
||||
showJoinDialog={showJoinDialog}
|
||||
setShowJoinDialog={setShowJoinDialog}
|
||||
onMembershipStatusChange={setIsMember}
|
||||
/>
|
||||
|
||||
<InviteCodeDialog
|
||||
relayInfo={relayInfo}
|
||||
showInviteCodeDialog={showInviteCodeDialog}
|
||||
setShowInviteCodeDialog={setShowInviteCodeDialog}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -503,6 +503,34 @@ export default {
|
||||
'My Packs': 'حزمي',
|
||||
'Adding...': 'جاري الإضافة...',
|
||||
'Removing...': 'جاري الإزالة...',
|
||||
Reload: 'إعادة التحميل'
|
||||
Reload: 'إعادة التحميل',
|
||||
'Request to Join Relay': 'طلب الانضمام إلى المرحل',
|
||||
'Leave Relay': 'مغادرة المرحل',
|
||||
Leave: 'مغادرة',
|
||||
'Are you sure you want to leave this relay?': 'هل أنت متأكد من أنك تريد مغادرة هذا المرحل؟',
|
||||
'Join request sent successfully': 'تم إرسال طلب الانضمام بنجاح',
|
||||
'Failed to send join request': 'فشل إرسال طلب الانضمام',
|
||||
'Leave request sent successfully': 'تم إرسال طلب المغادرة بنجاح',
|
||||
'Failed to send leave request': 'فشل إرسال طلب المغادرة',
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
'أدخل رمز الدعوة إذا كان لديك واحد. وإلا، اتركه فارغًا لإرسال طلب.',
|
||||
'Invite Code (Optional)': 'رمز الدعوة (اختياري)',
|
||||
'Enter invite code': 'أدخل رمز الدعوة',
|
||||
'Sending...': 'جاري الإرسال...',
|
||||
'Send Request': 'إرسال الطلب',
|
||||
'You can get an invite code from a relay member.': 'يمكنك الحصول على رمز دعوة من عضو المرحل.',
|
||||
'Enter the invite code you received from a relay member.': 'أدخل رمز الدعوة الذي تلقيته من عضو المرحل.',
|
||||
'Get Invite Code': 'الحصول على رمز الدعوة',
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
'شارك رمز الدعوة هذا مع الآخرين لدعوتهم للانضمام إلى هذا المرحل.',
|
||||
'Invite Code': 'رمز الدعوة',
|
||||
Copy: 'نسخ',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
'يمكن للآخرين استخدام رمز الدعوة هذا للانضمام إلى المرحل.',
|
||||
'No invite code available from this relay.': 'لا يوجد رمز دعوة متاح من هذا المرحل.',
|
||||
Close: 'إغلاق',
|
||||
'Failed to get invite code from relay': 'فشل الحصول على رمز الدعوة من المرحل',
|
||||
'Failed to get invite code': 'فشل الحصول على رمز الدعوة',
|
||||
'Invite code copied to clipboard': 'تم نسخ رمز الدعوة إلى الحافظة'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,6 +517,36 @@ export default {
|
||||
'My Packs': 'Meine Pakete',
|
||||
'Adding...': 'Wird hinzugefügt...',
|
||||
'Removing...': 'Wird entfernt...',
|
||||
Reload: 'Neu laden'
|
||||
Reload: 'Neu laden',
|
||||
'Request to Join Relay': 'Relay-Beitritt beantragen',
|
||||
'Leave Relay': 'Relay verlassen',
|
||||
Leave: 'Verlassen',
|
||||
'Are you sure you want to leave this relay?': 'Möchten Sie dieses Relay wirklich verlassen?',
|
||||
'Join request sent successfully': 'Beitrittsanfrage erfolgreich gesendet',
|
||||
'Failed to send join request': 'Fehler beim Senden der Beitrittsanfrage',
|
||||
'Leave request sent successfully': 'Austrittsanfrage erfolgreich gesendet',
|
||||
'Failed to send leave request': 'Fehler beim Senden der Austrittsanfrage',
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
'Geben Sie einen Einladungscode ein, falls Sie einen haben. Andernfalls lassen Sie es leer, um eine Anfrage zu senden.',
|
||||
'Invite Code (Optional)': 'Einladungscode (Optional)',
|
||||
'Enter invite code': 'Einladungscode eingeben',
|
||||
'Sending...': 'Wird gesendet...',
|
||||
'Send Request': 'Anfrage senden',
|
||||
'You can get an invite code from a relay member.':
|
||||
'Sie können einen Einladungscode von einem Relay-Mitglied erhalten.',
|
||||
'Enter the invite code you received from a relay member.':
|
||||
'Geben Sie den Einladungscode ein, den Sie von einem Relay-Mitglied erhalten haben.',
|
||||
'Get Invite Code': 'Einladungscode Erhalten',
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
'Teilen Sie diesen Einladungscode mit anderen, um sie einzuladen, diesem Relay beizutreten.',
|
||||
'Invite Code': 'Einladungscode',
|
||||
Copy: 'Kopieren',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
'Dieser Einladungscode kann von anderen verwendet werden, um dem Relay beizutreten.',
|
||||
'No invite code available from this relay.': 'Kein Einladungscode von diesem Relay verfügbar.',
|
||||
Close: 'Schließen',
|
||||
'Failed to get invite code from relay': 'Fehler beim Abrufen des Einladungscodes vom Relay',
|
||||
'Failed to get invite code': 'Fehler beim Abrufen des Einladungscodes',
|
||||
'Invite code copied to clipboard': 'Einladungscode in die Zwischenablage kopiert'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,6 +502,36 @@ export default {
|
||||
'My Packs': 'My Packs',
|
||||
'Adding...': 'Adding...',
|
||||
'Removing...': 'Removing...',
|
||||
Reload: 'Reload'
|
||||
Reload: 'Reload',
|
||||
'Request to Join Relay': 'Request to Join Relay',
|
||||
'Leave Relay': 'Leave Relay',
|
||||
Leave: 'Leave',
|
||||
'Are you sure you want to leave this relay?': 'Are you sure you want to leave this relay?',
|
||||
'Join request sent successfully': 'Join request sent successfully',
|
||||
'Failed to send join request': 'Failed to send join request',
|
||||
'Leave request sent successfully': 'Leave request sent successfully',
|
||||
'Failed to send leave request': 'Failed to send leave request',
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.',
|
||||
'Invite Code (Optional)': 'Invite Code (Optional)',
|
||||
'Enter invite code': 'Enter invite code',
|
||||
'Sending...': 'Sending...',
|
||||
'Send Request': 'Send Request',
|
||||
'You can get an invite code from a relay member.':
|
||||
'You can get an invite code from a relay member.',
|
||||
'Enter the invite code you received from a relay member.':
|
||||
'Enter the invite code you received from a relay member.',
|
||||
'Get Invite Code': 'Get Invite Code',
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
'Share this invite code with others to invite them to join this relay.',
|
||||
'Invite Code': 'Invite Code',
|
||||
Copy: 'Copy',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
'This invite code can be used by others to join the relay.',
|
||||
'No invite code available from this relay.': 'No invite code available from this relay.',
|
||||
Close: 'Close',
|
||||
'Failed to get invite code from relay': 'Failed to get invite code from relay',
|
||||
'Failed to get invite code': 'Failed to get invite code',
|
||||
'Invite code copied to clipboard': 'Invite code copied to clipboard'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,6 +511,36 @@ export default {
|
||||
'My Packs': 'Mis Paquetes',
|
||||
'Adding...': 'Añadiendo...',
|
||||
'Removing...': 'Eliminando...',
|
||||
Reload: 'Recargar'
|
||||
Reload: 'Recargar',
|
||||
'Request to Join Relay': 'Solicitar unirse al Relay',
|
||||
'Leave Relay': 'Salir del Relay',
|
||||
Leave: 'Salir',
|
||||
'Are you sure you want to leave this relay?': '¿Estás seguro de que quieres salir de este relay?',
|
||||
'Join request sent successfully': 'Solicitud de unión enviada con éxito',
|
||||
'Failed to send join request': 'Error al enviar solicitud de unión',
|
||||
'Leave request sent successfully': 'Solicitud de salida enviada con éxito',
|
||||
'Failed to send leave request': 'Error al enviar solicitud de salida',
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
'Ingresa un código de invitación si tienes uno. De lo contrario, déjalo en blanco para enviar una solicitud.',
|
||||
'Invite Code (Optional)': 'Código de Invitación (Opcional)',
|
||||
'Enter invite code': 'Ingresa el código de invitación',
|
||||
'Sending...': 'Enviando...',
|
||||
'Send Request': 'Enviar Solicitud',
|
||||
'You can get an invite code from a relay member.':
|
||||
'Puedes obtener un código de invitación de un miembro del relay.',
|
||||
'Enter the invite code you received from a relay member.':
|
||||
'Ingresa el código de invitación que recibiste de un miembro del relay.',
|
||||
'Get Invite Code': 'Obtener Código de Invitación',
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
'Comparte este código de invitación con otros para invitarlos a unirse a este relay.',
|
||||
'Invite Code': 'Código de Invitación',
|
||||
Copy: 'Copiar',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
'Este código de invitación puede ser usado por otros para unirse al relay.',
|
||||
'No invite code available from this relay.': 'No hay código de invitación disponible de este relay.',
|
||||
Close: 'Cerrar',
|
||||
'Failed to get invite code from relay': 'Error al obtener código de invitación del relay',
|
||||
'Failed to get invite code': 'Error al obtener código de invitación',
|
||||
'Invite code copied to clipboard': 'Código de invitación copiado al portapapeles'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,6 +506,36 @@ export default {
|
||||
'My Packs': 'بستههای من',
|
||||
'Adding...': 'در حال افزودن...',
|
||||
'Removing...': 'در حال حذف...',
|
||||
Reload: 'بازخوانی'
|
||||
Reload: 'بازخوانی',
|
||||
'Request to Join Relay': 'درخواست عضویت در رله',
|
||||
'Leave Relay': 'خروج از رله',
|
||||
Leave: 'خروج',
|
||||
'Are you sure you want to leave this relay?': 'آیا مطمئن هستید که میخواهید از این رله خارج شوید؟',
|
||||
'Join request sent successfully': 'درخواست عضویت با موفقیت ارسال شد',
|
||||
'Failed to send join request': 'ارسال درخواست عضویت ناموفق بود',
|
||||
'Leave request sent successfully': 'درخواست خروج با موفقیت ارسال شد',
|
||||
'Failed to send leave request': 'ارسال درخواست خروج ناموفق بود',
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
'اگر کد دعوت دارید وارد کنید. در غیر این صورت، آن را خالی بگذارید تا درخواست ارسال شود.',
|
||||
'Invite Code (Optional)': 'کد دعوت (اختیاری)',
|
||||
'Enter invite code': 'کد دعوت را وارد کنید',
|
||||
'Sending...': 'در حال ارسال...',
|
||||
'Send Request': 'ارسال درخواست',
|
||||
'You can get an invite code from a relay member.':
|
||||
'میتوانید کد دعوت را از یک عضو رله دریافت کنید.',
|
||||
'Enter the invite code you received from a relay member.':
|
||||
'کد دعوتی را که از یک عضو رله دریافت کردهاید وارد کنید.',
|
||||
'Get Invite Code': 'دریافت کد دعوت',
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
'این کد دعوت را با دیگران به اشتراک بگذارید تا آنها را به پیوستن به این رله دعوت کنید.',
|
||||
'Invite Code': 'کد دعوت',
|
||||
Copy: 'کپی',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
'این کد دعوت میتواند توسط دیگران برای پیوستن به رله استفاده شود.',
|
||||
'No invite code available from this relay.': 'هیچ کد دعوتی از این رله در دسترس نیست.',
|
||||
Close: 'بستن',
|
||||
'Failed to get invite code from relay': 'دریافت کد دعوت از رله ناموفق بود',
|
||||
'Failed to get invite code': 'دریافت کد دعوت ناموفق بود',
|
||||
'Invite code copied to clipboard': 'کد دعوت در کلیپبورد کپی شد'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,6 +516,36 @@ export default {
|
||||
'My Packs': 'Mes Packs',
|
||||
'Adding...': 'Ajout...',
|
||||
'Removing...': 'Suppression...',
|
||||
Reload: 'Recharger'
|
||||
Reload: 'Recharger',
|
||||
'Request to Join Relay': 'Demander à rejoindre le Relay',
|
||||
'Leave Relay': 'Quitter le Relay',
|
||||
Leave: 'Quitter',
|
||||
'Are you sure you want to leave this relay?': 'Êtes-vous sûr de vouloir quitter ce relay ?',
|
||||
'Join request sent successfully': "Demande d'adhésion envoyée avec succès",
|
||||
'Failed to send join request': "Échec de l'envoi de la demande d'adhésion",
|
||||
'Leave request sent successfully': 'Demande de départ envoyée avec succès',
|
||||
'Failed to send leave request': "Échec de l'envoi de la demande de départ",
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
"Entrez un code d'invitation si vous en avez un. Sinon, laissez-le vide pour envoyer une demande.",
|
||||
'Invite Code (Optional)': "Code d'Invitation (Optionnel)",
|
||||
'Enter invite code': "Entrez le code d'invitation",
|
||||
'Sending...': 'Envoi...',
|
||||
'Send Request': 'Envoyer la Demande',
|
||||
'You can get an invite code from a relay member.':
|
||||
"Vous pouvez obtenir un code d'invitation auprès d'un membre du relay.",
|
||||
'Enter the invite code you received from a relay member.':
|
||||
"Entrez le code d'invitation que vous avez reçu d'un membre du relay.",
|
||||
'Get Invite Code': "Obtenir un Code d'Invitation",
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
"Partagez ce code d'invitation avec d'autres pour les inviter à rejoindre ce relay.",
|
||||
'Invite Code': "Code d'Invitation",
|
||||
Copy: 'Copier',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
"Ce code d'invitation peut être utilisé par d'autres pour rejoindre le relay.",
|
||||
'No invite code available from this relay.': "Aucun code d'invitation disponible de ce relay.",
|
||||
Close: 'Fermer',
|
||||
'Failed to get invite code from relay': "Échec de l'obtention du code d'invitation du relay",
|
||||
'Failed to get invite code': "Échec de l'obtention du code d'invitation",
|
||||
'Invite code copied to clipboard': "Code d'invitation copié dans le presse-papiers"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,6 +508,36 @@ export default {
|
||||
'My Packs': 'मेरे पैक',
|
||||
'Adding...': 'जोड़ा जा रहा है...',
|
||||
'Removing...': 'हटाया जा रहा है...',
|
||||
Reload: 'रीलोड करें'
|
||||
Reload: 'रीलोड करें',
|
||||
'Request to Join Relay': 'रिले में शामिल होने का अनुरोध करें',
|
||||
'Leave Relay': 'रिले छोड़ें',
|
||||
Leave: 'छोड़ें',
|
||||
'Are you sure you want to leave this relay?': 'क्या आप वाकई इस रिले को छोड़ना चाहते हैं?',
|
||||
'Join request sent successfully': 'शामिल होने का अनुरोध सफलतापूर्वक भेजा गया',
|
||||
'Failed to send join request': 'शामिल होने का अनुरोध भेजने में विफल',
|
||||
'Leave request sent successfully': 'छोड़ने का अनुरोध सफलतापूर्वक भेजा गया',
|
||||
'Failed to send leave request': 'छोड़ने का अनुरोध भेजने में विफल',
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
'यदि आपके पास निमंत्रण कोड है तो दर्ज करें। अन्यथा, अनुरोध भेजने के लिए इसे खाली छोड़ दें।',
|
||||
'Invite Code (Optional)': 'निमंत्रण कोड (वैकल्पिक)',
|
||||
'Enter invite code': 'निमंत्रण कोड दर्ज करें',
|
||||
'Sending...': 'भेजा जा रहा है...',
|
||||
'Send Request': 'अनुरोध भेजें',
|
||||
'You can get an invite code from a relay member.':
|
||||
'आप एक रिले सदस्य से निमंत्रण कोड प्राप्त कर सकते हैं।',
|
||||
'Enter the invite code you received from a relay member.':
|
||||
'रिले सदस्य से प्राप्त निमंत्रण कोड दर्ज करें।',
|
||||
'Get Invite Code': 'निमंत्रण कोड प्राप्त करें',
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
'इस रिले में शामिल होने के लिए दूसरों को आमंत्रित करने के लिए इस निमंत्रण कोड को साझा करें।',
|
||||
'Invite Code': 'निमंत्रण कोड',
|
||||
Copy: 'कॉपी करें',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
'यह निमंत्रण कोड दूसरों द्वारा रिले में शामिल होने के लिए उपयोग किया जा सकता है।',
|
||||
'No invite code available from this relay.': 'इस रिले से कोई निमंत्रण कोड उपलब्ध नहीं है।',
|
||||
Close: 'बंद करें',
|
||||
'Failed to get invite code from relay': 'रिले से निमंत्रण कोड प्राप्त करने में विफल',
|
||||
'Failed to get invite code': 'निमंत्रण कोड प्राप्त करने में विफल',
|
||||
'Invite code copied to clipboard': 'निमंत्रण कोड क्लिपबोर्ड पर कॉपी किया गया'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,6 +503,36 @@ export default {
|
||||
'My Packs': 'Saját csomagjaim',
|
||||
'Adding...': 'Hozzáadás...',
|
||||
'Removing...': 'Eltávolítás...',
|
||||
Reload: 'Újratöltés'
|
||||
Reload: 'Újratöltés',
|
||||
'Request to Join Relay': 'Csatlakozási kérelem küldése a relay-hez',
|
||||
'Leave Relay': 'Relay elhagyása',
|
||||
Leave: 'Kilépés',
|
||||
'Are you sure you want to leave this relay?': 'Biztosan el szeretné hagyni ezt a relay-t?',
|
||||
'Join request sent successfully': 'Csatlakozási kérelem sikeresen elküldve',
|
||||
'Failed to send join request': 'Csatlakozási kérelem küldése sikertelen',
|
||||
'Leave request sent successfully': 'Kilépési kérelem sikeresen elküldve',
|
||||
'Failed to send leave request': 'Kilépési kérelem küldése sikertelen',
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
'Írjon be egy meghívókódot, ha van. Ellenkező esetben hagyja üresen a kérelem elküldéséhez.',
|
||||
'Invite Code (Optional)': 'Meghívókód (opcionális)',
|
||||
'Enter invite code': 'Írja be a meghívókódot',
|
||||
'Sending...': 'Küldés...',
|
||||
'Send Request': 'Kérelem küldése',
|
||||
'You can get an invite code from a relay member.':
|
||||
'Meghívókódot kaphat egy relay tagtól.',
|
||||
'Enter the invite code you received from a relay member.':
|
||||
'Írja be a relay tagtól kapott meghívókódot.',
|
||||
'Get Invite Code': 'Meghívókód Lekérése',
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
'Ossza meg ezt a meghívókódot másokkal, hogy meghívja őket ehhez a relay-hez.',
|
||||
'Invite Code': 'Meghívókód',
|
||||
Copy: 'Másolás',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
'Ezt a meghívókódot mások használhatják a relay-hez való csatlakozáshoz.',
|
||||
'No invite code available from this relay.': 'Nincs elérhető meghívókód ettől a relay-től.',
|
||||
Close: 'Bezárás',
|
||||
'Failed to get invite code from relay': 'Nem sikerült lekérni a meghívókódot a relay-től',
|
||||
'Failed to get invite code': 'Nem sikerült lekérni a meghívókódot',
|
||||
'Invite code copied to clipboard': 'Meghívókód vágólapra másolva'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,6 +511,36 @@ export default {
|
||||
'My Packs': 'I Miei Pacchetti',
|
||||
'Adding...': 'Aggiunta...',
|
||||
'Removing...': 'Rimozione...',
|
||||
Reload: 'Ricarica'
|
||||
Reload: 'Ricarica',
|
||||
'Request to Join Relay': 'Richiedi di unirti al Relay',
|
||||
'Leave Relay': 'Lascia il Relay',
|
||||
Leave: 'Esci',
|
||||
'Are you sure you want to leave this relay?': 'Sei sicuro di voler lasciare questo relay?',
|
||||
'Join request sent successfully': 'Richiesta di adesione inviata con successo',
|
||||
'Failed to send join request': "Impossibile inviare la richiesta di adesione",
|
||||
'Leave request sent successfully': 'Richiesta di uscita inviata con successo',
|
||||
'Failed to send leave request': "Impossibile inviare la richiesta di uscita",
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
"Inserisci un codice di invito se ne hai uno. Altrimenti, lascialo vuoto per inviare una richiesta.",
|
||||
'Invite Code (Optional)': 'Codice di Invito (Opzionale)',
|
||||
'Enter invite code': 'Inserisci il codice di invito',
|
||||
'Sending...': 'Invio...',
|
||||
'Send Request': 'Invia Richiesta',
|
||||
'You can get an invite code from a relay member.':
|
||||
'Puoi ottenere un codice di invito da un membro del relay.',
|
||||
'Enter the invite code you received from a relay member.':
|
||||
'Inserisci il codice di invito che hai ricevuto da un membro del relay.',
|
||||
'Get Invite Code': 'Ottieni Codice di Invito',
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
'Condividi questo codice di invito con altri per invitarli a unirsi a questo relay.',
|
||||
'Invite Code': 'Codice di Invito',
|
||||
Copy: 'Copia',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
'Questo codice di invito può essere utilizzato da altri per unirsi al relay.',
|
||||
'No invite code available from this relay.': 'Nessun codice di invito disponibile da questo relay.',
|
||||
Close: 'Chiudi',
|
||||
'Failed to get invite code from relay': 'Impossibile ottenere il codice di invito dal relay',
|
||||
'Failed to get invite code': 'Impossibile ottenere il codice di invito',
|
||||
'Invite code copied to clipboard': 'Codice di invito copiato negli appunti'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,6 +507,34 @@ export default {
|
||||
'My Packs': 'マイパック',
|
||||
'Adding...': '追加中...',
|
||||
'Removing...': '削除中...',
|
||||
Reload: '再読み込み'
|
||||
Reload: '再読み込み',
|
||||
'Request to Join Relay': 'リレーへの参加をリクエスト',
|
||||
'Leave Relay': 'リレーを退出',
|
||||
Leave: '退出',
|
||||
'Are you sure you want to leave this relay?': 'このリレーを退出してもよろしいですか?',
|
||||
'Join request sent successfully': '参加リクエストを送信しました',
|
||||
'Failed to send join request': '参加リクエストの送信に失敗しました',
|
||||
'Leave request sent successfully': '退出リクエストを送信しました',
|
||||
'Failed to send leave request': '退出リクエストの送信に失敗しました',
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
'招待コードをお持ちの場合は入力してください。それ以外の場合は空白のままリクエストを送信してください。',
|
||||
'Invite Code (Optional)': '招待コード(オプション)',
|
||||
'Enter invite code': '招待コードを入力',
|
||||
'Sending...': '送信中...',
|
||||
'Send Request': 'リクエストを送信',
|
||||
'You can get an invite code from a relay member.': 'リレーメンバーから招待コードを取得できます。',
|
||||
'Enter the invite code you received from a relay member.': 'リレーメンバーから受け取った招待コードを入力してください。',
|
||||
'Get Invite Code': '招待コードを取得',
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
'この招待コードを他の人と共有して、このリレーへの参加を招待してください。',
|
||||
'Invite Code': '招待コード',
|
||||
Copy: 'コピー',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
'この招待コードは他の人がリレーに参加するために使用できます。',
|
||||
'No invite code available from this relay.': 'このリレーから利用可能な招待コードはありません。',
|
||||
Close: '閉じる',
|
||||
'Failed to get invite code from relay': 'リレーから招待コードの取得に失敗しました',
|
||||
'Failed to get invite code': '招待コードの取得に失敗しました',
|
||||
'Invite code copied to clipboard': '招待コードをクリップボードにコピーしました'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,6 +507,34 @@ export default {
|
||||
'My Packs': '내 팩',
|
||||
'Adding...': '추가 중...',
|
||||
'Removing...': '제거 중...',
|
||||
Reload: '다시 불러오기'
|
||||
Reload: '다시 불러오기',
|
||||
'Request to Join Relay': '릴레이 가입 요청',
|
||||
'Leave Relay': '릴레이 떠나기',
|
||||
Leave: '나가기',
|
||||
'Are you sure you want to leave this relay?': '이 릴레이를 떠나시겠습니까?',
|
||||
'Join request sent successfully': '가입 요청을 성공적으로 보냈습니다',
|
||||
'Failed to send join request': '가입 요청 전송 실패',
|
||||
'Leave request sent successfully': '떠나기 요청을 성공적으로 보냈습니다',
|
||||
'Failed to send leave request': '떠나기 요청 전송 실패',
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
'초대 코드가 있으면 입력하세요. 그렇지 않으면 비워두고 요청을 보내세요.',
|
||||
'Invite Code (Optional)': '초대 코드 (선택 사항)',
|
||||
'Enter invite code': '초대 코드 입력',
|
||||
'Sending...': '전송 중...',
|
||||
'Send Request': '요청 보내기',
|
||||
'You can get an invite code from a relay member.': '릴레이 회원으로부터 초대 코드를 받을 수 있습니다.',
|
||||
'Enter the invite code you received from a relay member.': '릴레이 회원으로부터 받은 초대 코드를 입력하세요.',
|
||||
'Get Invite Code': '초대 코드 받기',
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
'이 초대 코드를 다른 사람과 공유하여 이 릴레이에 초대하세요.',
|
||||
'Invite Code': '초대 코드',
|
||||
Copy: '복사',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
'이 초대 코드는 다른 사람이 릴레이에 가입하는 데 사용할 수 있습니다.',
|
||||
'No invite code available from this relay.': '이 릴레이에서 사용 가능한 초대 코드가 없습니다.',
|
||||
Close: '닫기',
|
||||
'Failed to get invite code from relay': '릴레이에서 초대 코드 가져오기 실패',
|
||||
'Failed to get invite code': '초대 코드 가져오기 실패',
|
||||
'Invite code copied to clipboard': '초대 코드가 클립보드에 복사되었습니다'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,6 +511,36 @@ export default {
|
||||
'My Packs': 'Moje Pakiety',
|
||||
'Adding...': 'Dodawanie...',
|
||||
'Removing...': 'Usuwanie...',
|
||||
Reload: 'Przeładuj'
|
||||
Reload: 'Przeładuj',
|
||||
'Request to Join Relay': 'Poproś o dołączenie do przekaźnika',
|
||||
'Leave Relay': 'Opuść przekaźnik',
|
||||
Leave: 'Opuść',
|
||||
'Are you sure you want to leave this relay?': 'Czy na pewno chcesz opuścić ten przekaźnik?',
|
||||
'Join request sent successfully': 'Prośba o dołączenie wysłana pomyślnie',
|
||||
'Failed to send join request': 'Nie udało się wysłać prośby o dołączenie',
|
||||
'Leave request sent successfully': 'Prośba o opuszczenie wysłana pomyślnie',
|
||||
'Failed to send leave request': 'Nie udało się wysłać prośby o opuszczenie',
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
'Wprowadź kod zaproszenia, jeśli go masz. W przeciwnym razie pozostaw puste, aby wysłać prośbę.',
|
||||
'Invite Code (Optional)': 'Kod zaproszenia (opcjonalnie)',
|
||||
'Enter invite code': 'Wprowadź kod zaproszenia',
|
||||
'Sending...': 'Wysyłanie...',
|
||||
'Send Request': 'Wyślij prośbę',
|
||||
'You can get an invite code from a relay member.':
|
||||
'Możesz uzyskać kod zaproszenia od członka przekaźnika.',
|
||||
'Enter the invite code you received from a relay member.':
|
||||
'Wprowadź kod zaproszenia otrzymany od członka przekaźnika.',
|
||||
'Get Invite Code': 'Uzyskaj Kod Zaproszenia',
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
'Udostępnij ten kod zaproszenia innym, aby zaprosić ich do dołączenia do tego przekaźnika.',
|
||||
'Invite Code': 'Kod Zaproszenia',
|
||||
Copy: 'Kopiuj',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
'Ten kod zaproszenia może być używany przez innych do dołączenia do przekaźnika.',
|
||||
'No invite code available from this relay.': 'Brak dostępnego kodu zaproszenia z tego przekaźnika.',
|
||||
Close: 'Zamknij',
|
||||
'Failed to get invite code from relay': 'Nie udało się uzyskać kodu zaproszenia z przekaźnika',
|
||||
'Failed to get invite code': 'Nie udało się uzyskać kodu zaproszenia',
|
||||
'Invite code copied to clipboard': 'Kod zaproszenia skopiowany do schowka'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,6 +508,36 @@ export default {
|
||||
'My Packs': 'Meus Pacotes',
|
||||
'Adding...': 'Adicionando...',
|
||||
'Removing...': 'Removendo...',
|
||||
Reload: 'Recarregar'
|
||||
Reload: 'Recarregar',
|
||||
'Request to Join Relay': 'Solicitar entrada no Relay',
|
||||
'Leave Relay': 'Sair do Relay',
|
||||
Leave: 'Sair',
|
||||
'Are you sure you want to leave this relay?': 'Tem certeza de que deseja sair deste relay?',
|
||||
'Join request sent successfully': 'Solicitação de entrada enviada com sucesso',
|
||||
'Failed to send join request': 'Falha ao enviar solicitação de entrada',
|
||||
'Leave request sent successfully': 'Solicitação de saída enviada com sucesso',
|
||||
'Failed to send leave request': 'Falha ao enviar solicitação de saída',
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
'Digite um código de convite se tiver um. Caso contrário, deixe em branco para enviar uma solicitação.',
|
||||
'Invite Code (Optional)': 'Código de Convite (Opcional)',
|
||||
'Enter invite code': 'Digite o código de convite',
|
||||
'Sending...': 'Enviando...',
|
||||
'Send Request': 'Enviar Solicitação',
|
||||
'You can get an invite code from a relay member.':
|
||||
'Você pode obter um código de convite de um membro do relay.',
|
||||
'Enter the invite code you received from a relay member.':
|
||||
'Digite o código de convite que você recebeu de um membro do relay.',
|
||||
'Get Invite Code': 'Obter Código de Convite',
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
'Compartilhe este código de convite com outros para convidá-los a participar deste relay.',
|
||||
'Invite Code': 'Código de Convite',
|
||||
Copy: 'Copiar',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
'Este código de convite pode ser usado por outros para participar do relay.',
|
||||
'No invite code available from this relay.': 'Nenhum código de convite disponível deste relay.',
|
||||
Close: 'Fechar',
|
||||
'Failed to get invite code from relay': 'Falha ao obter código de convite do relay',
|
||||
'Failed to get invite code': 'Falha ao obter código de convite',
|
||||
'Invite code copied to clipboard': 'Código de convite copiado para a área de transferência'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,6 +511,36 @@ export default {
|
||||
'My Packs': 'Os Meus Pacotes',
|
||||
'Adding...': 'A adicionar...',
|
||||
'Removing...': 'A remover...',
|
||||
Reload: 'Recarregar'
|
||||
Reload: 'Recarregar',
|
||||
'Request to Join Relay': 'Solicitar adesão ao Relay',
|
||||
'Leave Relay': 'Sair do Relay',
|
||||
Leave: 'Sair',
|
||||
'Are you sure you want to leave this relay?': 'Tem a certeza de que deseja sair deste relay?',
|
||||
'Join request sent successfully': 'Pedido de adesão enviado com sucesso',
|
||||
'Failed to send join request': 'Falha ao enviar pedido de adesão',
|
||||
'Leave request sent successfully': 'Pedido de saída enviado com sucesso',
|
||||
'Failed to send leave request': 'Falha ao enviar pedido de saída',
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
'Introduza um código de convite se tiver um. Caso contrário, deixe em branco para enviar um pedido.',
|
||||
'Invite Code (Optional)': 'Código de Convite (Opcional)',
|
||||
'Enter invite code': 'Introduza o código de convite',
|
||||
'Sending...': 'A enviar...',
|
||||
'Send Request': 'Enviar Pedido',
|
||||
'You can get an invite code from a relay member.':
|
||||
'Pode obter um código de convite de um membro do relay.',
|
||||
'Enter the invite code you received from a relay member.':
|
||||
'Introduza o código de convite que recebeu de um membro do relay.',
|
||||
'Get Invite Code': 'Obter Código de Convite',
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
'Partilhe este código de convite com outros para os convidar a aderir a este relay.',
|
||||
'Invite Code': 'Código de Convite',
|
||||
Copy: 'Copiar',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
'Este código de convite pode ser usado por outros para aderir ao relay.',
|
||||
'No invite code available from this relay.': 'Nenhum código de convite disponível deste relay.',
|
||||
Close: 'Fechar',
|
||||
'Failed to get invite code from relay': 'Falha ao obter código de convite do relay',
|
||||
'Failed to get invite code': 'Falha ao obter código de convite',
|
||||
'Invite code copied to clipboard': 'Código de convite copiado para a área de transferência'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,6 +513,36 @@ export default {
|
||||
'My Packs': 'Мои наборы',
|
||||
'Adding...': 'Добавление...',
|
||||
'Removing...': 'Удаление...',
|
||||
Reload: 'Перезагрузить'
|
||||
Reload: 'Перезагрузить',
|
||||
'Request to Join Relay': 'Запросить присоединение к релею',
|
||||
'Leave Relay': 'Покинуть релей',
|
||||
Leave: 'Выйти',
|
||||
'Are you sure you want to leave this relay?': 'Вы уверены, что хотите покинуть этот релей?',
|
||||
'Join request sent successfully': 'Запрос на присоединение успешно отправлен',
|
||||
'Failed to send join request': 'Не удалось отправить запрос на присоединение',
|
||||
'Leave request sent successfully': 'Запрос на выход успешно отправлен',
|
||||
'Failed to send leave request': 'Не удалось отправить запрос на выход',
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
'Введите код приглашения, если он у вас есть. В противном случае оставьте поле пустым для отправки запроса.',
|
||||
'Invite Code (Optional)': 'Код приглашения (необязательно)',
|
||||
'Enter invite code': 'Введите код приглашения',
|
||||
'Sending...': 'Отправка...',
|
||||
'Send Request': 'Отправить запрос',
|
||||
'You can get an invite code from a relay member.':
|
||||
'Вы можете получить код приглашения у члена релея.',
|
||||
'Enter the invite code you received from a relay member.':
|
||||
'Введите код приглашения, который вы получили от члена релея.',
|
||||
'Get Invite Code': 'Получить Код Приглашения',
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
'Поделитесь этим кодом приглашения с другими, чтобы пригласить их присоединиться к этому релею.',
|
||||
'Invite Code': 'Код Приглашения',
|
||||
Copy: 'Копировать',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
'Этот код приглашения может быть использован другими для присоединения к релею.',
|
||||
'No invite code available from this relay.': 'Нет доступного кода приглашения от этого релея.',
|
||||
Close: 'Закрыть',
|
||||
'Failed to get invite code from relay': 'Не удалось получить код приглашения от релея',
|
||||
'Failed to get invite code': 'Не удалось получить код приглашения',
|
||||
'Invite code copied to clipboard': 'Код приглашения скопирован в буфер обмена'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,6 +501,34 @@ export default {
|
||||
'My Packs': 'แพ็คของฉัน',
|
||||
'Adding...': 'กำลังเพิ่ม...',
|
||||
'Removing...': 'กำลังลบ...',
|
||||
Reload: 'โหลดใหม่'
|
||||
Reload: 'โหลดใหม่',
|
||||
'Request to Join Relay': 'ขอเข้าร่วมรีเลย์',
|
||||
'Leave Relay': 'ออกจากรีเลย์',
|
||||
Leave: 'ออก',
|
||||
'Are you sure you want to leave this relay?': 'คุณแน่ใจหรือไม่ว่าต้องการออกจากรีเลย์นี้?',
|
||||
'Join request sent successfully': 'ส่งคำขอเข้าร่วมสำเร็จแล้ว',
|
||||
'Failed to send join request': 'การส่งคำขอเข้าร่วมล้มเหลว',
|
||||
'Leave request sent successfully': 'ส่งคำขอออกสำเร็จแล้ว',
|
||||
'Failed to send leave request': 'การส่งคำขอออกล้มเหลว',
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
'ป้อนรหัสเชิญหากคุณมี มิฉะนั้นให้เว้นว่างไว้เพื่อส่งคำขอ',
|
||||
'Invite Code (Optional)': 'รหัสเชิญ (ไม่บังคับ)',
|
||||
'Enter invite code': 'ป้อนรหัสเชิญ',
|
||||
'Sending...': 'กำลังส่ง...',
|
||||
'Send Request': 'ส่งคำขอ',
|
||||
'You can get an invite code from a relay member.': 'คุณสามารถรับรหัสเชิญจากสมาชิกรีเลย์',
|
||||
'Enter the invite code you received from a relay member.': 'ป้อนรหัสเชิญที่คุณได้รับจากสมาชิกรีเลย์',
|
||||
'Get Invite Code': 'รับรหัสเชิญ',
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
'แชร์รหัสเชิญนี้กับผู้อื่นเพื่อเชิญพวกเขาเข้าร่วมรีเลย์นี้',
|
||||
'Invite Code': 'รหัสเชิญ',
|
||||
Copy: 'คัดลอก',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
'รหัสเชิญนี้สามารถใช้โดยผู้อื่นเพื่อเข้าร่วมรีเลย์',
|
||||
'No invite code available from this relay.': 'ไม่มีรหัสเชิญที่ใช้ได้จากรีเลย์นี้',
|
||||
Close: 'ปิด',
|
||||
'Failed to get invite code from relay': 'ไม่สามารถรับรหัสเชิญจากรีเลย์',
|
||||
'Failed to get invite code': 'ไม่สามารถรับรหัสเชิญ',
|
||||
'Invite code copied to clipboard': 'คัดลอกรหัสเชิญไปยังคลิปบอร์ดแล้ว'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,6 +498,34 @@ export default {
|
||||
'My Packs': '我的表情包',
|
||||
'Adding...': '添加中...',
|
||||
'Removing...': '移除中...',
|
||||
Reload: '重新加载'
|
||||
Reload: '重新加载',
|
||||
'Request to Join Relay': '申请加入中继器',
|
||||
'Leave Relay': '离开中继器',
|
||||
Leave: '离开',
|
||||
'Are you sure you want to leave this relay?': '您确定要离开此中继器吗?',
|
||||
'Join request sent successfully': '加入请求已成功发送',
|
||||
'Failed to send join request': '发送加入请求失败',
|
||||
'Leave request sent successfully': '离开请求已成功发送',
|
||||
'Failed to send leave request': '发送离开请求失败',
|
||||
'Enter an invite code if you have one. Otherwise, leave it blank to send a request.':
|
||||
'如果您有邀请码,请输入。否则,留空以发送请求。',
|
||||
'Invite Code (Optional)': '邀请码(可选)',
|
||||
'Enter invite code': '输入邀请码',
|
||||
'Sending...': '发送中...',
|
||||
'Send Request': '发送请求',
|
||||
'You can get an invite code from a relay member.': '您可以从中继器成员获取邀请码。',
|
||||
'Enter the invite code you received from a relay member.': '输入您从中继器成员处获得的邀请码。',
|
||||
'Get Invite Code': '获取邀请码',
|
||||
'Share this invite code with others to invite them to join this relay.':
|
||||
'将此邀请码分享给他人以邀请他们加入此中继器。',
|
||||
'Invite Code': '邀请码',
|
||||
Copy: '复制',
|
||||
'This invite code can be used by others to join the relay.':
|
||||
'此邀请码可供他人用于加入中继器。',
|
||||
'No invite code available from this relay.': '此中继器没有可用的邀请码。',
|
||||
Close: '关闭',
|
||||
'Failed to get invite code from relay': '从中继器获取邀请码失败',
|
||||
'Failed to get invite code': '获取邀请码失败',
|
||||
'Invite code copied to clipboard': '邀请码已复制到剪贴板'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,6 +493,25 @@ export function createRelayReviewDraftEvent(
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/nostr-protocol/nips/blob/master/43.md
|
||||
export function createJoinDraftEvent(inviteCode: string): TDraftEvent {
|
||||
return {
|
||||
kind: 28934,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [['claim', inviteCode], ['-']],
|
||||
content: ''
|
||||
}
|
||||
}
|
||||
|
||||
export function createLeaveDraftEvent(): TDraftEvent {
|
||||
return {
|
||||
kind: 28936,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [['-']],
|
||||
content: ''
|
||||
}
|
||||
}
|
||||
|
||||
function generateImetaTags(imageUrls: string[]) {
|
||||
return imageUrls
|
||||
.map((imageUrl) => {
|
||||
|
||||
@@ -363,3 +363,8 @@ export function getRetainedEvent(a: Event, b: Event): Event {
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Descending sort
|
||||
export function sortEventsDesc(events: Event[]): Event[] {
|
||||
return events.sort((a, b) => compareEvents(b, a))
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ export function checkSearchRelay(relayInfo: TRelayInfo | undefined) {
|
||||
return relayInfo?.supported_nips?.includes(50)
|
||||
}
|
||||
|
||||
export function checkNip43Support(relayInfo: TRelayInfo | undefined) {
|
||||
return relayInfo?.supported_nips?.includes(43) && !!relayInfo.pubkey
|
||||
}
|
||||
|
||||
export function filterOutBigRelays(relayUrls: string[]) {
|
||||
return relayUrls.filter((url) => !BIG_RELAY_URLS.includes(url))
|
||||
}
|
||||
|
||||
127
src/services/relay-membership.service.ts
Normal file
127
src/services/relay-membership.service.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { sortEventsDesc } from '@/lib/event'
|
||||
import { isValidPubkey } from '@/lib/pubkey'
|
||||
import client from '@/services/client.service'
|
||||
import DataLoader from 'dataloader'
|
||||
import { Filter } from 'nostr-tools'
|
||||
|
||||
/**
|
||||
* NIP-43: Relay Access Metadata and Requests
|
||||
* https://github.com/nostr-protocol/nips/blob/master/43.md
|
||||
*/
|
||||
class RelayMembershipService {
|
||||
private static instance: RelayMembershipService
|
||||
private membershipListCache: Map<string, Promise<Set<string>>> = new Map()
|
||||
private membershipListDataLoader = new DataLoader<
|
||||
{ url: string; pubkey: string },
|
||||
Set<string>,
|
||||
string
|
||||
>(
|
||||
async (params) => {
|
||||
return Promise.all(params.map(({ url, pubkey }) => this.fetchMembershipList(url, pubkey)))
|
||||
},
|
||||
{ cacheKeyFn: (key) => key.url, cacheMap: this.membershipListCache }
|
||||
)
|
||||
|
||||
public static getInstance(): RelayMembershipService {
|
||||
if (!RelayMembershipService.instance) {
|
||||
RelayMembershipService.instance = new RelayMembershipService()
|
||||
}
|
||||
return RelayMembershipService.instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user is a member of a relay that supports NIP-43
|
||||
* @param relayUrl The relay URL
|
||||
* @param userPubkey The user's public key
|
||||
* @param relayPubkey The relay's public key from NIP-11
|
||||
* @returns Membership status
|
||||
*/
|
||||
async checkMembership(
|
||||
relayUrl: string,
|
||||
userPubkey: string,
|
||||
relayPubkey?: string
|
||||
): Promise<boolean> {
|
||||
if (!relayPubkey) {
|
||||
return false
|
||||
}
|
||||
|
||||
const memberSet = await this.membershipListDataLoader.load({
|
||||
url: relayUrl,
|
||||
pubkey: relayPubkey
|
||||
})
|
||||
|
||||
return memberSet.has(userPubkey)
|
||||
}
|
||||
|
||||
private async fetchMembershipList(relayUrl: string, relayPubkey: string): Promise<Set<string>> {
|
||||
try {
|
||||
const filter: Filter = {
|
||||
kinds: [13534],
|
||||
authors: [relayPubkey],
|
||||
limit: 1
|
||||
}
|
||||
|
||||
const events = await client.fetchEvents([relayUrl], filter)
|
||||
|
||||
if (events.length === 0) {
|
||||
return new Set()
|
||||
}
|
||||
|
||||
const membershipEvent = sortEventsDesc(events)[0]
|
||||
const members = membershipEvent.tags
|
||||
.filter((tag) => tag[0] === 'member' && isValidPubkey(tag[1]))
|
||||
.map((tag) => tag[1])
|
||||
|
||||
return new Set(members)
|
||||
} catch (error) {
|
||||
console.error('Error checking relay membership:', error)
|
||||
return new Set()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request an invite code from a relay (kind 28935)
|
||||
* @param relayUrl The relay URL
|
||||
* @param relayPubkey The relay's public key from NIP-11
|
||||
* @returns Invite code or null
|
||||
*/
|
||||
async requestInviteCode(relayUrl: string, relayPubkey: string): Promise<string | null> {
|
||||
try {
|
||||
const filter: Filter = {
|
||||
kinds: [28935],
|
||||
authors: [relayPubkey],
|
||||
limit: 1
|
||||
}
|
||||
|
||||
const events = await client.fetchEvents([relayUrl], filter)
|
||||
|
||||
if (events.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const inviteEvent = events[0]
|
||||
const claimTag = inviteEvent.tags.find((tag) => tag[0] === 'claim')
|
||||
return claimTag?.[1] ?? null
|
||||
} catch (error) {
|
||||
console.error('Error requesting invite code:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async addNewMember(relayUrl: string, newMemberPubkey: string) {
|
||||
const cache = await this.membershipListCache.get(relayUrl)
|
||||
if (cache) {
|
||||
cache.add(newMemberPubkey)
|
||||
}
|
||||
}
|
||||
|
||||
async removeMember(relayUrl: string, memberPubkey: string) {
|
||||
const cache = await this.membershipListCache.get(relayUrl)
|
||||
if (cache) {
|
||||
cache.delete(memberPubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const instance = RelayMembershipService.getInstance()
|
||||
export default instance
|
||||
Reference in New Issue
Block a user