diff --git a/src/App.tsx b/src/App.tsx
index f819b4f2..817e4e25 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -18,7 +18,6 @@ import { NostrProvider } from '@/providers/NostrProvider'
import { ReplyProvider } from '@/providers/ReplyProvider'
import { ScreenSizeProvider } from '@/providers/ScreenSizeProvider'
import { ThemeProvider } from '@/providers/ThemeProvider'
-import { TranslationServiceProvider } from '@/providers/TranslationServiceProvider'
import { UserPreferencesProvider } from '@/providers/UserPreferencesProvider'
import { UserTrustProvider } from '@/providers/UserTrustProvider'
import { ZapProvider } from '@/providers/ZapProvider'
@@ -33,7 +32,6 @@ export default function App(): JSX.Element {
-
@@ -60,8 +58,7 @@ export default function App(): JSX.Element {
-
-
+
diff --git a/src/components/Content/index.tsx b/src/components/Content/index.tsx
index 7e0e312e..bf6ed0a8 100644
--- a/src/components/Content/index.tsx
+++ b/src/components/Content/index.tsx
@@ -1,4 +1,4 @@
-import { useTranslatedEvent, useMediaExtraction } from '@/hooks'
+import { useMediaExtraction } from '@/hooks'
import {
EmbeddedEmojiParser,
EmbeddedEventParser,
@@ -78,8 +78,7 @@ export default function Content({
className?: string
mustLoadMedia?: boolean
}) {
- const translatedEvent = useTranslatedEvent(event?.id)
- const _content = translatedEvent?.content ?? event?.content ?? content
+ const _content = event?.content ?? content
// Use unified media extraction service
const extractedMedia = useMediaExtraction(event, _content)
diff --git a/src/components/ContentPreview/HighlightPreview.tsx b/src/components/ContentPreview/HighlightPreview.tsx
index b1656831..fa3de8a1 100644
--- a/src/components/ContentPreview/HighlightPreview.tsx
+++ b/src/components/ContentPreview/HighlightPreview.tsx
@@ -1,4 +1,3 @@
-import { useTranslatedEvent } from '@/hooks'
import { getEmojiInfosFromEmojiTags } from '@/lib/tag'
import { cn } from '@/lib/utils'
import { Event } from 'nostr-tools'
@@ -14,14 +13,13 @@ export default function HighlightPreview({
className?: string
}) {
const { t } = useTranslation()
- const translatedEvent = useTranslatedEvent(event.id)
const emojiInfos = useMemo(() => getEmojiInfosFromEmojiTags(event.tags), [event])
return (
[{t('Highlight')}]{' '}
diff --git a/src/components/ContentPreview/NormalContentPreview.tsx b/src/components/ContentPreview/NormalContentPreview.tsx
index 55456896..6ce25eea 100644
--- a/src/components/ContentPreview/NormalContentPreview.tsx
+++ b/src/components/ContentPreview/NormalContentPreview.tsx
@@ -1,4 +1,3 @@
-import { useTranslatedEvent } from '@/hooks'
import { getEmojiInfosFromEmojiTags } from '@/lib/tag'
import { Event } from 'nostr-tools'
import { useMemo } from 'react'
@@ -11,12 +10,11 @@ export default function NormalContentPreview({
event: Event
className?: string
}) {
- const translatedEvent = useTranslatedEvent(event?.id)
const emojiInfos = useMemo(() => getEmojiInfosFromEmojiTags(event?.tags), [event])
return (
diff --git a/src/components/ContentPreview/PollPreview.tsx b/src/components/ContentPreview/PollPreview.tsx
index f3b72827..d457183f 100644
--- a/src/components/ContentPreview/PollPreview.tsx
+++ b/src/components/ContentPreview/PollPreview.tsx
@@ -1,5 +1,4 @@
import { POLL_TYPE } from '@/constants'
-import { useTranslatedEvent } from '@/hooks'
import { getPollMetadataFromEvent } from '@/lib/event-metadata'
import { getEmojiInfosFromEmojiTags } from '@/lib/tag'
import { cn } from '@/lib/utils'
@@ -10,13 +9,9 @@ import Content from './Content'
export default function PollPreview({ event, className }: { event: Event; className?: string }) {
const { t } = useTranslation()
- const translatedEvent = useTranslatedEvent(event.id)
const emojiInfos = useMemo(() => getEmojiInfosFromEmojiTags(event.tags), [event])
- const poll = useMemo(
- () => getPollMetadataFromEvent(translatedEvent ?? event),
- [event, translatedEvent]
- )
- const content = (translatedEvent?.content ?? event.content)?.trim()
+ const poll = useMemo(() => getPollMetadataFromEvent(event), [event])
+ const content = event.content?.trim()
return (
diff --git a/src/components/Note/Poll.tsx b/src/components/Note/Poll.tsx
index 1feb994d..59dab008 100644
--- a/src/components/Note/Poll.tsx
+++ b/src/components/Note/Poll.tsx
@@ -1,6 +1,5 @@
import { Button } from '@/components/ui/button'
import { BIG_RELAY_URLS, POLL_TYPE } from '@/constants'
-import { useTranslatedEvent } from '@/hooks'
import { useFetchPollResults } from '@/hooks/useFetchPollResults'
import { createPollResponseDraftEvent } from '@/lib/draft-event'
import { getPollMetadataFromEvent } from '@/lib/event-metadata'
@@ -18,16 +17,12 @@ import { showPublishingFeedback, showSimplePublishSuccess } from '@/lib/publishi
export default function Poll({ event, className }: { event: Event; className?: string }) {
const { t } = useTranslation()
- const translatedEvent = useTranslatedEvent(event.id)
const { pubkey, publish, startLogin } = useNostr()
const [isVoting, setIsVoting] = useState(false)
const [selectedOptionIds, setSelectedOptionIds] = useState
([])
const pollResults = useFetchPollResults(event.id)
const [isLoadingResults, setIsLoadingResults] = useState(false)
- const poll = useMemo(
- () => getPollMetadataFromEvent(translatedEvent ?? event),
- [event, translatedEvent]
- )
+ const poll = useMemo(() => getPollMetadataFromEvent(event), [event])
const votedOptionIds = useMemo(() => {
if (!pollResults || !pubkey) return []
return Object.entries(pollResults.results)
diff --git a/src/components/Note/index.tsx b/src/components/Note/index.tsx
index 90c68508..c51b35eb 100644
--- a/src/components/Note/index.tsx
+++ b/src/components/Note/index.tsx
@@ -17,7 +17,6 @@ import { FormattedTimestamp } from '../FormattedTimestamp'
import Nip05 from '../Nip05'
import NoteOptions from '../NoteOptions'
import ParentNotePreview from '../ParentNotePreview'
-import TranslateButton from '../TranslateButton'
import UserAvatar from '../UserAvatar'
import Username from '../Username'
import { MessageSquare } from 'lucide-react'
@@ -266,7 +265,6 @@ export default function Note({
)}
-
{size === 'normal' && (
{
- const detected = detectLanguage(about)
- if (!detected) return false
- if (detected === 'und') return true
- return !i18n.language.startsWith(detected)
- }, [about, i18n.language])
- const [translatedAbout, setTranslatedAbout] = useState(null)
- const [translating, setTranslating] = useState(false)
- const aboutNodes = useMemo(() => {
- if (!about) return null
+ const aboutNodes = parseContent(about ?? '', [
+ EmbeddedWebsocketUrlParser,
+ EmbeddedUrlParser,
+ EmbeddedHashtagParser,
+ EmbeddedMentionParser
+ ]).map((node, index) => {
+ if (node.type === 'url') {
+ return
+ }
+ if (node.type === 'websocket-url') {
+ return
+ }
+ if (node.type === 'hashtag') {
+ return
+ }
+ if (node.type === 'mention') {
+ return
+ }
+ return node.data
+ })
- const nodes = parseContent(translatedAbout ?? about, [
- EmbeddedWebsocketUrlParser,
- EmbeddedUrlParser,
- EmbeddedHashtagParser,
- EmbeddedMentionParser
- ])
- return nodes.map((node, index) => {
- if (node.type === 'url') {
- return
- }
- if (node.type === 'websocket-url') {
- return
- }
- if (node.type === 'hashtag') {
- return
- }
- if (node.type === 'mention') {
- return
- }
- return node.data
- })
- }, [about, translatedAbout])
-
- const handleTranslate = async () => {
- if (translating || translatedAbout) return
- setTranslating(true)
- translateText(about ?? '')
- .then((translated) => {
- setTranslatedAbout(translated)
- })
- .catch((error) => {
- toast.error(
- 'Translation failed: ' +
- (error.message || 'An error occurred while translating the about')
- )
- })
- .finally(() => {
- setTranslating(false)
- })
- }
-
- const handleShowOriginal = () => {
- setTranslatedAbout(null)
- }
-
- return (
-
-
{aboutNodes}
- {needTranslation && (
-
- {translating ? (
-
{t('Translating...')}
- ) : translatedAbout === null ? (
-
{
- e.stopPropagation()
- handleTranslate()
- }}
- >
- {t('Translate')}
-
- ) : (
-
{
- e.stopPropagation()
- handleShowOriginal()
- }}
- >
- {t('Show original')}
-
- )}
-
- )}
-
- )
+ return {aboutNodes}
}
diff --git a/src/components/RelayInfo/RelayReviewCard.tsx b/src/components/RelayInfo/RelayReviewCard.tsx
index 2ba837d2..7441bcfa 100644
--- a/src/components/RelayInfo/RelayReviewCard.tsx
+++ b/src/components/RelayInfo/RelayReviewCard.tsx
@@ -9,7 +9,6 @@ import ContentPreview from '../ContentPreview'
import { FormattedTimestamp } from '../FormattedTimestamp'
import Nip05 from '../Nip05'
import Stars from '../Stars'
-import TranslateButton from '../TranslateButton'
import { SimpleUserAvatar } from '../UserAvatar'
import { SimpleUsername } from '../Username'
@@ -53,9 +52,6 @@ export default function RelayReviewCard({
-
-
-
diff --git a/src/components/ReplyNote/index.tsx b/src/components/ReplyNote/index.tsx
index 9eee6f23..c0be38ef 100644
--- a/src/components/ReplyNote/index.tsx
+++ b/src/components/ReplyNote/index.tsx
@@ -17,7 +17,6 @@ import Nip05 from '../Nip05'
import NoteOptions from '../NoteOptions'
import NoteStats from '../NoteStats'
import ParentNotePreview from '../ParentNotePreview'
-import TranslateButton from '../TranslateButton'
import UserAvatar from '../UserAvatar'
import Username from '../Username'
@@ -94,7 +93,6 @@ export default function ReplyNote({
-
diff --git a/src/components/TranslateButton/index.tsx b/src/components/TranslateButton/index.tsx
deleted file mode 100644
index ae3f015c..00000000
--- a/src/components/TranslateButton/index.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import { ExtendedKind } from '@/constants'
-import { useTranslatedEvent } from '@/hooks'
-import { toTranslation } from '@/lib/link'
-import { cn, detectLanguage } from '@/lib/utils'
-import { useSecondaryPage } from '@/PageManager'
-import { useTranslationService } from '@/providers/TranslationServiceProvider'
-import { Languages, Loader } from 'lucide-react'
-import { Event, kinds } from 'nostr-tools'
-import { useMemo, useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import { toast } from 'sonner'
-
-export default function TranslateButton({
- event,
- className
-}: {
- event: Event
- className?: string
-}) {
- const { i18n } = useTranslation()
- const { push } = useSecondaryPage()
- const { translateEvent, showOriginalEvent } = useTranslationService()
- const [translating, setTranslating] = useState(false)
- const translatedEvent = useTranslatedEvent(event.id)
- const supported = useMemo(
- () =>
- [
- kinds.ShortTextNote,
- kinds.Highlights,
- ExtendedKind.COMMENT,
- ExtendedKind.PICTURE,
- ExtendedKind.POLL,
- ExtendedKind.RELAY_REVIEW
- ].includes(event.kind),
- [event]
- )
-
- const needTranslation = useMemo(() => {
- const detected = detectLanguage(event.content)
- if (!detected) return false
- if (detected === 'und') return true
- return !i18n.language.startsWith(detected)
- }, [event, i18n.language])
-
- if (!supported || !needTranslation) {
- return null
- }
-
- const handleTranslate = async () => {
- if (translating) return
-
- setTranslating(true)
- await translateEvent(event)
- .catch((error) => {
- toast.error(
- 'Translation failed: ' + (error.message || 'An error occurred while translating the note')
- )
- if (error.message === 'Insufficient balance.') {
- push(toTranslation())
- }
- })
- .finally(() => {
- setTranslating(false)
- })
- }
-
- const showOriginal = () => {
- showOriginalEvent(event.id)
- }
-
- return (
- {
- e.stopPropagation()
- if (translatedEvent) {
- showOriginal()
- } else {
- handleTranslate()
- }
- }}
- >
- {translating ? (
-
- ) : (
-
- )}
-
- )
-}
diff --git a/src/constants.ts b/src/constants.ts
index 1b3878e3..b321e647 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -37,7 +37,6 @@ export const StorageKey = {
AUTOPLAY: 'autoplay',
HIDE_UNTRUSTED_INTERACTIONS: 'hideUntrustedInteractions',
HIDE_UNTRUSTED_NOTIFICATIONS: 'hideUntrustedNotifications',
- TRANSLATION_SERVICE_CONFIG_MAP: 'translationServiceConfigMap',
MEDIA_UPLOAD_SERVICE_CONFIG_MAP: 'mediaUploadServiceConfigMap',
HIDE_UNTRUSTED_NOTES: 'hideUntrustedNotes',
DEFAULT_SHOW_NSFW: 'defaultShowNsfw',
diff --git a/src/hooks/index.tsx b/src/hooks/index.tsx
index 227d80af..652e395d 100644
--- a/src/hooks/index.tsx
+++ b/src/hooks/index.tsx
@@ -6,5 +6,4 @@ export * from './useFetchRelayInfo'
export * from './useFetchRelayInfos'
export * from './useFetchRelayList'
export * from './useSearchProfiles'
-export * from './useTranslatedEvent'
export * from './useMediaExtraction'
diff --git a/src/hooks/useTranslatedEvent.tsx b/src/hooks/useTranslatedEvent.tsx
deleted file mode 100644
index 0bcf6b80..00000000
--- a/src/hooks/useTranslatedEvent.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { useTranslationService } from '@/providers/TranslationServiceProvider'
-import { Event } from 'nostr-tools'
-import { useEffect, useMemo, useState } from 'react'
-
-export function useTranslatedEvent(eventId?: string) {
- const { translatedEventIdSet, getTranslatedEvent } = useTranslationService()
- const translated = useMemo(() => {
- return eventId ? translatedEventIdSet.has(eventId) : false
- }, [eventId, translatedEventIdSet])
- const [translatedEvent, setTranslatedEvent] = useState(null)
-
- useEffect(() => {
- if (translated && eventId) {
- setTranslatedEvent(getTranslatedEvent(eventId))
- } else {
- setTranslatedEvent(null)
- }
- }, [translated, eventId])
-
- return translatedEvent
-}
diff --git a/src/pages/secondary/TranslationPage/JumbleTranslate/AccountInfo.tsx b/src/pages/secondary/TranslationPage/JumbleTranslate/AccountInfo.tsx
deleted file mode 100644
index b733baa6..00000000
--- a/src/pages/secondary/TranslationPage/JumbleTranslate/AccountInfo.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { Button } from '@/components/ui/button'
-import { Input } from '@/components/ui/input'
-import { JUMBLE_API_BASE_URL } from '@/constants'
-import { useNostr } from '@/providers/NostrProvider'
-import { Check, Copy, Eye, EyeOff } from 'lucide-react'
-import { useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import { useJumbleTranslateAccount } from './JumbleTranslateAccountProvider'
-import RegenerateApiKeyButton from './RegenerateApiKeyButton'
-import TopUp from './TopUp'
-
-export function AccountInfo() {
- const { t } = useTranslation()
- const { pubkey, startLogin } = useNostr()
- const { account } = useJumbleTranslateAccount()
- const [showApiKey, setShowApiKey] = useState(false)
- const [copied, setCopied] = useState(false)
-
- if (!pubkey) {
- return (
-
- startLogin()}>{t('Login')}
-
- )
- }
-
- return (
-
- {/* Balance display in characters */}
-
-
{t('Balance')}
-
-
{account?.balance.toLocaleString() ?? '0'}
-
{t('characters')}
-
-
-
- {/* API Key section with visibility toggle and copy functionality */}
-
-
API key
-
-
- setShowApiKey(!showApiKey)}>
- {showApiKey ? : }
-
- {
- if (!account?.api_key) return
- navigator.clipboard.writeText(account.api_key)
- setCopied(true)
- setTimeout(() => setCopied(false), 4000)
- }}
- >
- {copied ? : }
-
-
-
-
- {t('jumbleTranslateApiKeyDescription', {
- serviceUrl: new URL('/v1/translation', JUMBLE_API_BASE_URL).toString()
- })}
-
-
-
-
-
- )
-}
diff --git a/src/pages/secondary/TranslationPage/JumbleTranslate/JumbleTranslateAccountProvider.tsx b/src/pages/secondary/TranslationPage/JumbleTranslate/JumbleTranslateAccountProvider.tsx
deleted file mode 100644
index 78c7f777..00000000
--- a/src/pages/secondary/TranslationPage/JumbleTranslate/JumbleTranslateAccountProvider.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import { useNostr } from '@/providers/NostrProvider'
-import { useTranslationService } from '@/providers/TranslationServiceProvider'
-import { TTranslationAccount } from '@/types'
-import { createContext, useContext, useEffect, useState } from 'react'
-import { toast } from 'sonner'
-
-type TJumbleTranslateAccountContext = {
- account: TTranslationAccount | null
- getAccount: () => Promise
- regenerateApiKey: () => Promise
-}
-
-export const JumbleTranslateAccountContext = createContext<
- TJumbleTranslateAccountContext | undefined
->(undefined)
-
-export const useJumbleTranslateAccount = () => {
- const context = useContext(JumbleTranslateAccountContext)
- if (!context) {
- throw new Error(
- 'useJumbleTranslateAccount must be used within a JumbleTranslateAccountProvider'
- )
- }
- return context
-}
-
-export function JumbleTranslateAccountProvider({ children }: { children: React.ReactNode }) {
- const { pubkey } = useNostr()
- const { getAccount: _getAccount, regenerateApiKey: _regenerateApiKey } = useTranslationService()
- const [account, setAccount] = useState(null)
-
- useEffect(() => {
- setAccount(null)
- if (!pubkey) return
-
- setTimeout(() => {
- getAccount()
- }, 100)
- }, [pubkey])
-
- const regenerateApiKey = async (): Promise => {
- try {
- if (!account) {
- await getAccount()
- }
- const newApiKey = await _regenerateApiKey()
- if (newApiKey) {
- setAccount((prev) => {
- if (!prev) return prev
- return {
- ...prev,
- api_key: newApiKey
- }
- })
- }
- } catch (error) {
- toast.error(
- 'Failed to regenerate Jumble translation API key: ' +
- (error instanceof Error
- ? error.message
- : 'An error occurred while regenerating the API key')
- )
- setAccount(null)
- }
- }
-
- const getAccount = async (): Promise => {
- try {
- const data = await _getAccount()
- if (data) {
- setAccount(data)
- }
- } catch (error) {
- toast.error(
- 'Failed to fetch Jumble translation account: ' +
- (error instanceof Error ? error.message : 'An error occurred while fetching the account')
- )
- setAccount(null)
- }
- }
-
- return (
-
- {children}
-
- )
-}
diff --git a/src/pages/secondary/TranslationPage/JumbleTranslate/RegenerateApiKeyButton.tsx b/src/pages/secondary/TranslationPage/JumbleTranslate/RegenerateApiKeyButton.tsx
deleted file mode 100644
index 2dc68b53..00000000
--- a/src/pages/secondary/TranslationPage/JumbleTranslate/RegenerateApiKeyButton.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { Button } from '@/components/ui/button'
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger
-} from '@/components/ui/dialog'
-import { Loader, RotateCcw } from 'lucide-react'
-import { useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import { useJumbleTranslateAccount } from './JumbleTranslateAccountProvider'
-
-export default function RegenerateApiKeyButton() {
- const { t } = useTranslation()
- const { account, regenerateApiKey } = useJumbleTranslateAccount()
- const [resettingApiKey, setResettingApiKey] = useState(false)
- const [showResetDialog, setShowResetDialog] = useState(false)
-
- const handleRegenerateApiKey = async () => {
- if (resettingApiKey || !account) return
-
- setResettingApiKey(true)
- await regenerateApiKey()
- setShowResetDialog(false)
- setResettingApiKey(false)
- }
-
- return (
-
-
-
-
-
-
-
-
- {t('Reset API key')}
-
- {t('Are you sure you want to reset your API key? This action cannot be undone.')}
-
-
- {t('Warning')}: {' '}
- {t(
- 'Your current API key will become invalid immediately, and any applications using it will stop working until you update them with the new key.'
- )}
-
-
-
- setShowResetDialog(false)}
- disabled={resettingApiKey}
- >
- {t('Cancel')}
-
-
- {resettingApiKey && }
- {t('Reset API key')}
-
-
-
-
- )
-}
diff --git a/src/pages/secondary/TranslationPage/JumbleTranslate/TopUp.tsx b/src/pages/secondary/TranslationPage/JumbleTranslate/TopUp.tsx
deleted file mode 100644
index 581957f1..00000000
--- a/src/pages/secondary/TranslationPage/JumbleTranslate/TopUp.tsx
+++ /dev/null
@@ -1,164 +0,0 @@
-import { Button } from '@/components/ui/button'
-import { Input } from '@/components/ui/input'
-import { cn } from '@/lib/utils'
-import { useNostr } from '@/providers/NostrProvider'
-import transaction from '@/services/transaction.service'
-import { closeModal, launchPaymentModal } from '@getalby/bitcoin-connect-react'
-import { Loader } from 'lucide-react'
-import { useState } from 'react'
-import { toast } from 'sonner'
-import { useJumbleTranslateAccount } from './JumbleTranslateAccountProvider'
-import { useTranslation } from 'react-i18next'
-
-export default function TopUp() {
- const { t } = useTranslation()
- const { pubkey } = useNostr()
- const { getAccount } = useJumbleTranslateAccount()
- const [topUpLoading, setTopUpLoading] = useState(false)
- const [topUpAmount, setTopUpAmount] = useState(1000)
- const [selectedAmount, setSelectedAmount] = useState(1000)
-
- const presetAmounts = [
- { amount: 1_000, text: '1k' },
- { amount: 5_000, text: '5k' },
- { amount: 10_000, text: '10k' },
- { amount: 25_000, text: '25k' },
- { amount: 50_000, text: '50k' },
- { amount: 100_000, text: '100k' }
- ]
- const charactersPerUnit = 100 // 1 unit = 100 characters
-
- const calculateCharacters = (amount: number) => {
- return amount * charactersPerUnit
- }
-
- const handlePresetClick = (amount: number) => {
- setSelectedAmount(amount)
- setTopUpAmount(amount)
- }
-
- const handleInputChange = (value: string) => {
- const numValue = parseInt(value) || 0
- setTopUpAmount(numValue)
- setSelectedAmount(numValue >= 1000 ? numValue : null)
- }
-
- const handleTopUp = async (amount: number | null) => {
- if (topUpLoading || !pubkey || !amount || amount < 1000) return
-
- setTopUpLoading(true)
- try {
- const { transactionId, invoiceId } = await transaction.createTransaction(pubkey, amount)
-
- let checkPaymentInterval: ReturnType | undefined = undefined
- const { setPaid } = launchPaymentModal({
- invoice: invoiceId,
- onCancelled: () => {
- clearInterval(checkPaymentInterval)
- setTopUpLoading(false)
- }
- })
-
- let failedCount = 0
- checkPaymentInterval = setInterval(async () => {
- try {
- const { state } = await transaction.checkTransaction(transactionId)
- if (state === 'pending') return
-
- clearInterval(checkPaymentInterval)
- setTopUpLoading(false)
-
- if (state === 'settled') {
- setPaid({ preimage: '' }) // Preimage is not returned, but we can assume payment is successful
- getAccount() // Refresh account balance
- } else {
- closeModal()
- toast.error('The invoice has expired or the payment was not successful')
- }
- } catch (err) {
- failedCount++
- if (failedCount <= 3) return
-
- clearInterval(checkPaymentInterval)
- setTopUpLoading(false)
- toast.error(
- 'Top up failed: ' +
- (err instanceof Error ? err.message : 'An error occurred while topping up')
- )
- }
- }, 2000)
- } catch (err) {
- setTopUpLoading(false)
- toast.error(
- 'Top up failed: ' +
- (err instanceof Error ? err.message : 'An error occurred while topping up')
- )
- }
- }
-
- return (
-
-
{t('Top up')}
-
- {/* Preset amounts */}
-
- {presetAmounts.map(({ amount, text }) => (
- handlePresetClick(amount)}
- className={cn(
- 'flex flex-col h-auto py-3 hover:bg-primary/10',
- selectedAmount === amount && 'border border-primary bg-primary/10'
- )}
- >
-
- {text} {t('sats')}
-
-
- {calculateCharacters(amount).toLocaleString()} {t('characters')}
-
-
- ))}
-
-
- {/* Custom amount input */}
-
-
- handleInputChange(e.target.value)}
- min={1000}
- step={1000}
- className="w-40"
- />
- {t('sats')}
-
- {selectedAmount && selectedAmount >= 1000 && (
-
- {t('Will receive: {n} characters', {
- n: calculateCharacters(selectedAmount).toLocaleString()
- })}
-
- )}
-
-
-
handleTopUp(selectedAmount)}
- >
- {topUpLoading && }
- {selectedAmount && selectedAmount >= 1000
- ? t('Top up {n} sats', {
- n: selectedAmount?.toLocaleString()
- })
- : t('Minimum top up is {n} sats', {
- n: new Number(1000).toLocaleString()
- })}
-
-
- )
-}
diff --git a/src/pages/secondary/TranslationPage/JumbleTranslate/index.tsx b/src/pages/secondary/TranslationPage/JumbleTranslate/index.tsx
deleted file mode 100644
index f85e59c7..00000000
--- a/src/pages/secondary/TranslationPage/JumbleTranslate/index.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { AccountInfo } from './AccountInfo'
-import { JumbleTranslateAccountProvider } from './JumbleTranslateAccountProvider'
-
-export default function JumbleTranslate() {
- return (
-
-
-
- )
-}
diff --git a/src/pages/secondary/TranslationPage/LibreTranslate/index.tsx b/src/pages/secondary/TranslationPage/LibreTranslate/index.tsx
deleted file mode 100644
index c81d6970..00000000
--- a/src/pages/secondary/TranslationPage/LibreTranslate/index.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { Input } from '@/components/ui/input'
-import { Label } from '@/components/ui/label'
-import { useTranslationService } from '@/providers/TranslationServiceProvider'
-import { useEffect, useRef, useState } from 'react'
-import { useTranslation } from 'react-i18next'
-
-export default function LibreTranslate() {
- const { t } = useTranslation()
- const { config, updateConfig } = useTranslationService()
- const [server, setServer] = useState(
- config.service === 'libre_translate' ? (config.server ?? '') : ''
- )
- const [apiKey, setApiKey] = useState(
- config.service === 'libre_translate' ? (config.api_key ?? '') : ''
- )
- const initialized = useRef(false)
-
- useEffect(() => {
- if (!initialized.current) {
- initialized.current = true
- return
- }
-
- updateConfig({
- service: 'libre_translate',
- server,
- api_key: apiKey
- })
- }, [server, apiKey])
-
- return (
-
- )
-}
diff --git a/src/pages/secondary/TranslationPage/index.tsx b/src/pages/secondary/TranslationPage/index.tsx
index 21dc6855..b711e1fd 100644
--- a/src/pages/secondary/TranslationPage/index.tsx
+++ b/src/pages/secondary/TranslationPage/index.tsx
@@ -1,74 +1,23 @@
-import { Label } from '@/components/ui/label'
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue
-} from '@/components/ui/select'
-import { LocalizedLanguageNames } from '@/i18n'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
-import { useTranslationService } from '@/providers/TranslationServiceProvider'
-import { TLanguage } from '@/types'
-import { forwardRef, useState } from 'react'
+import { forwardRef } from 'react'
import { useTranslation } from 'react-i18next'
-import JumbleTranslate from './JumbleTranslate'
-import LibreTranslate from './LibreTranslate'
-const TranslationPage = forwardRef(({ index, hideTitlebar = false }: { index?: number; hideTitlebar?: boolean }, ref) => {
- const { t, i18n } = useTranslation()
- const { config, updateConfig } = useTranslationService()
- const [language, setLanguage] = useState(i18n.language as TLanguage)
+const TranslationPage = forwardRef(
+ ({ index, hideTitlebar = false }: { index?: number; hideTitlebar?: boolean }, ref) => {
+ const { t } = useTranslation()
- const handleLanguageChange = (value: TLanguage) => {
- i18n.changeLanguage(value)
- setLanguage(value)
- }
-
- return (
-
-
-
-
- {t('Languages')}
-
-
-
-
-
-
- {Object.entries(LocalizedLanguageNames).map(([key, value]) => (
-
- {value}
-
- ))}
-
-
+ return (
+
+
+
+ {t(
+ 'To translate notes and other content, use your browser’s built-in translation. For example: right-click the page and choose “Translate to…”, or use the translate icon in the address bar.'
+ )}
+
-
-
- {t('Service')}
-
- {
- updateConfig({ service: newService as 'jumble' | 'libre_translate' })
- }}
- >
-
-
-
-
- Jumble
- LibreTranslate
-
-
-
- {config.service === 'jumble' ? : }
-
-
- )
-})
+
+ )
+ }
+)
TranslationPage.displayName = 'TranslationPage'
export default TranslationPage
diff --git a/src/providers/TranslationServiceProvider.tsx b/src/providers/TranslationServiceProvider.tsx
deleted file mode 100644
index 7902ef51..00000000
--- a/src/providers/TranslationServiceProvider.tsx
+++ /dev/null
@@ -1,235 +0,0 @@
-import { ExtendedKind } from '@/constants'
-import { getPollMetadataFromEvent } from '@/lib/event-metadata'
-import libreTranslate from '@/services/libre-translate.service'
-import storage from '@/services/local-storage.service'
-import translation from '@/services/translation.service'
-import { TTranslationAccount, TTranslationServiceConfig } from '@/types'
-import { Event, kinds } from 'nostr-tools'
-import { createContext, useContext, useEffect, useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import { useNostr } from './NostrProvider'
-
-const translatedEventCache: Map
= new Map()
-const translatedTextCache: Map = new Map()
-
-type TTranslationServiceContext = {
- config: TTranslationServiceConfig
- translatedEventIdSet: Set
- translateText: (text: string) => Promise
- translateEvent: (event: Event) => Promise
- getTranslatedEvent: (eventId: string) => Event | null
- showOriginalEvent: (eventId: string) => void
- getAccount: () => Promise
- regenerateApiKey: () => Promise
- updateConfig: (newConfig: TTranslationServiceConfig) => void
-}
-
-const TranslationServiceContext = createContext(undefined)
-
-export const useTranslationService = () => {
- const context = useContext(TranslationServiceContext)
- if (!context) {
- throw new Error('useTranslation must be used within a TranslationProvider')
- }
- return context
-}
-
-export function TranslationServiceProvider({ children }: { children: React.ReactNode }) {
- const { i18n } = useTranslation()
- const [config, setConfig] = useState({ service: 'jumble' })
- const { pubkey, startLogin } = useNostr()
- const [translatedEventIdSet, setTranslatedEventIdSet] = useState>(new Set())
-
- useEffect(() => {
- translation.changeCurrentPubkey(pubkey)
- const config = storage.getTranslationServiceConfig(pubkey)
- setConfig(config)
- }, [pubkey])
-
- const getAccount = async (): Promise => {
- if (config.service !== 'jumble') return
- if (!pubkey) {
- startLogin()
- return
- }
- return await translation.getAccount()
- }
-
- const regenerateApiKey = async (): Promise => {
- if (config.service !== 'jumble') return
- if (!pubkey) {
- startLogin()
- return
- }
- return await translation.regenerateApiKey()
- }
-
- const getTranslatedEvent = (eventId: string): Event | null => {
- const target = i18n.language
- const cacheKey = target + '_' + eventId
- return translatedEventCache.get(cacheKey) ?? null
- }
-
- const translate = async (text: string, target: string): Promise => {
- if (config.service === 'jumble') {
- return await translation.translate(text, target)
- } else {
- return await libreTranslate.translate(text, target, config.server, config.api_key)
- }
- }
-
- const translateText = async (text: string): Promise => {
- if (!text) {
- return text
- }
-
- const target = i18n.language
- const cacheKey = target + '_' + text
- const cache = translatedTextCache.get(cacheKey)
- if (cache) {
- return cache
- }
-
- const translatedText = await translate(text, target)
- translatedTextCache.set(cacheKey, translatedText)
- return translatedText
- }
-
- const translateHighlightEvent = async (event: Event): Promise => {
- const target = i18n.language
- const comment = event.tags.find((tag) => tag[0] === 'comment')?.[1]
-
- const texts = {
- content: event.content,
- comment
- }
- const joinedText = joinTexts(texts)
- if (!joinedText) return event
-
- const translatedText = await translate(joinedText, target)
- const translatedTexts = splitTranslatedText(translatedText)
- return {
- ...event,
- content: translatedTexts.content ?? event.content,
- tags: event.tags.map((tag) =>
- tag[0] === 'comment' ? ['comment', translatedTexts.comment ?? tag[1]] : tag
- )
- }
- }
-
- const translatePollEvent = async (event: Event): Promise => {
- const target = i18n.language
- const pollMetadata = getPollMetadataFromEvent(event)
-
- const texts: Record = {
- question: event.content,
- ...pollMetadata?.options.reduce(
- (acc, option) => {
- acc[option.id] = option.label
- return acc
- },
- {} as Record
- )
- }
- const joinedText = joinTexts(texts)
- if (!joinedText) return event
-
- const translatedText = await translate(joinedText, target)
- const translatedTexts = splitTranslatedText(translatedText)
- return {
- ...event,
- content: translatedTexts.question ?? '',
- tags: event.tags.map((tag) =>
- tag[0] === 'option' ? ['option', tag[1], translatedTexts[tag[1]] ?? tag[2]] : tag
- )
- }
- }
-
- const translateEvent = async (event: Event): Promise => {
- if (config.service === 'jumble' && !pubkey) {
- startLogin()
- return
- }
-
- const target = i18n.language
- const cacheKey = target + '_' + event.id
- const cache = translatedEventCache.get(cacheKey)
- if (cache) {
- setTranslatedEventIdSet((prev) => new Set(prev.add(event.id)))
- return cache
- }
-
- let translatedEvent: Event | undefined
- if (event.kind === kinds.Highlights) {
- translatedEvent = await translateHighlightEvent(event)
- } else if (event.kind === ExtendedKind.POLL) {
- translatedEvent = await translatePollEvent(event)
- } else {
- const translatedText = await translate(event.content, target)
- if (!translatedText) {
- return
- }
- translatedEvent = { ...event, content: translatedText }
- }
-
- translatedEventCache.set(cacheKey, translatedEvent)
- setTranslatedEventIdSet((prev) => new Set(prev.add(event.id)))
- return translatedEvent
- }
-
- const showOriginalEvent = (eventId: string) => {
- setTranslatedEventIdSet((prev) => {
- const newSet = new Set(prev)
- newSet.delete(eventId)
- return newSet
- })
- }
-
- const updateConfig = (newConfig: TTranslationServiceConfig) => {
- setConfig(newConfig)
- storage.setTranslationServiceConfig(newConfig, pubkey)
- }
-
- return (
-
- {children}
-
- )
-}
-
-function joinTexts(texts: Record): string {
- return (
- Object.entries(texts).filter(([, content]) => content && content.trim() !== '') as [
- string,
- string
- ][]
- )
- .map(([key, content]) => `=== ${key} ===\n${content.trim()}\n=== ${key} ===`)
- .join('\n\n')
-}
-
-function splitTranslatedText(translated: string) {
- const regex = /=== (.+?) ===\n([\s\S]*?)\n=== \1 ===/g
- const results: Record = {}
-
- let match: RegExpExecArray | null
- while ((match = regex.exec(translated)) !== null) {
- const key = match[1].trim()
- const content = match[2].trim()
- results[key] = content
- }
-
- return results
-}
diff --git a/src/services/client.service.ts b/src/services/client.service.ts
index d9b0711e..216b2ed0 100644
--- a/src/services/client.service.ts
+++ b/src/services/client.service.ts
@@ -2152,11 +2152,6 @@ class ClientService extends EventTarget {
await this.updateReplaceableEventCache(evt)
}
- /** Fetch profile (kind 0) event; uses replaceable cache and IndexedDB. */
- async fetchProfileEvent(pubkey: string) {
- return await this.fetchReplaceableEvent(pubkey, kinds.Metadata)
- }
-
/**
* Force-refresh profile (kind 0) and payment info (kind 10133) cache for a pubkey:
* clears in-memory cache and IndexedDB so the next fetch loads from relays.
diff --git a/src/services/libre-translate.service.ts b/src/services/libre-translate.service.ts
deleted file mode 100644
index d17c5a3d..00000000
--- a/src/services/libre-translate.service.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-class LibreTranslateService {
- static instance: LibreTranslateService
-
- constructor() {
- if (!LibreTranslateService.instance) {
- LibreTranslateService.instance = this
- }
- return LibreTranslateService.instance
- }
-
- async translate(
- text: string,
- target: string,
- server?: string,
- api_key?: string
- ): Promise {
- if (!text) {
- return text
- }
- if (!server) {
- throw new Error('LibreTranslate server address is not configured')
- }
- const url = new URL('/translate', server).toString()
- const response = await fetch(url, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ q: text, target, source: 'auto', api_key })
- })
- const data = await response.json()
- if (!response.ok) {
- throw new Error(data.error ?? 'Failed to translate')
- }
- const translatedText = data.translatedText
- if (!translatedText) {
- throw new Error('Translation failed')
- }
- return translatedText
- }
-}
-
-const instance = new LibreTranslateService()
-export default instance
diff --git a/src/services/local-storage.service.ts b/src/services/local-storage.service.ts
index 48d16d0f..098745f7 100644
--- a/src/services/local-storage.service.ts
+++ b/src/services/local-storage.service.ts
@@ -20,7 +20,6 @@ import {
TNotificationStyle,
TRelaySet,
TThemeSetting,
- TTranslationServiceConfig
} from '@/types'
class LocalStorageService {
@@ -43,7 +42,6 @@ class LocalStorageService {
private hideUntrustedInteractions: boolean = false
private hideUntrustedNotifications: boolean = false
private hideUntrustedNotes: boolean = false
- private translationServiceConfigMap: Record = {}
private mediaUploadServiceConfigMap: Record = {}
private defaultShowNsfw: boolean = false
private dismissedTooManyRelaysAlert: boolean = false
@@ -162,13 +160,6 @@ class LocalStorageService {
? storedHideUntrustedNotes === 'true'
: hideUntrustedEvents
- const translationServiceConfigMapStr = window.localStorage.getItem(
- StorageKey.TRANSLATION_SERVICE_CONFIG_MAP
- )
- if (translationServiceConfigMapStr) {
- this.translationServiceConfigMap = JSON.parse(translationServiceConfigMapStr)
- }
-
const mediaUploadServiceConfigMapStr = window.localStorage.getItem(
StorageKey.MEDIA_UPLOAD_SERVICE_CONFIG_MAP
)
@@ -546,18 +537,6 @@ class LocalStorageService {
window.localStorage.setItem(StorageKey.HIDE_UNTRUSTED_NOTES, hideUntrustedNotes.toString())
}
- getTranslationServiceConfig(pubkey?: string | null) {
- return this.translationServiceConfigMap[pubkey ?? '_'] ?? { service: 'jumble' }
- }
-
- setTranslationServiceConfig(config: TTranslationServiceConfig, pubkey?: string | null) {
- this.translationServiceConfigMap[pubkey ?? '_'] = config
- window.localStorage.setItem(
- StorageKey.TRANSLATION_SERVICE_CONFIG_MAP,
- JSON.stringify(this.translationServiceConfigMap)
- )
- }
-
getMediaUploadServiceConfig(pubkey?: string | null): TMediaUploadServiceConfig {
const defaultConfig = { type: 'nip96', service: this.mediaUploadService } as const
if (!pubkey) {
diff --git a/src/services/translation.service.ts b/src/services/translation.service.ts
deleted file mode 100644
index 52af8e2a..00000000
--- a/src/services/translation.service.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-import { JUMBLE_API_BASE_URL } from '@/constants'
-import client from '@/services/client.service'
-import { TTranslationAccount } from '@/types'
-
-class TranslationService {
- static instance: TranslationService
-
- private apiKeyMap: Record = {}
- private currentPubkey: string | null = null
-
- constructor() {
- if (!TranslationService.instance) {
- TranslationService.instance = this
- }
- return TranslationService.instance
- }
-
- async getAccount(): Promise {
- if (!this.currentPubkey) {
- throw new Error('Please login first')
- }
- const apiKey = this.apiKeyMap[this.currentPubkey]
- const path = '/v1/translation/account'
- const method = 'GET'
- let auth: string | undefined
- if (!apiKey) {
- auth = await client.signHttpAuth(
- new URL(path, JUMBLE_API_BASE_URL).toString(),
- method,
- 'Auth to get Jumble translation service account'
- )
- }
- const act = await this._fetch({
- path,
- method,
- auth,
- retryWhenUnauthorized: !auth
- })
-
- if (act.api_key && act.pubkey) {
- this.apiKeyMap[act.pubkey] = act.api_key
- }
-
- return act
- }
-
- async regenerateApiKey(): Promise {
- try {
- const data = await this._fetch({
- path: '/v1/translation/regenerate-api-key',
- method: 'POST'
- })
- if (data.api_key && this.currentPubkey) {
- this.apiKeyMap[this.currentPubkey] = data.api_key
- }
- return data.api_key
- } catch (error) {
- const errMsg = error instanceof Error ? error.message : ''
- throw new Error(errMsg || 'Failed to regenerate API key')
- }
- }
-
- async translate(text: string, target: string): Promise {
- if (!text) {
- return text
- }
- try {
- const data = await this._fetch({
- path: '/v1/translation/translate',
- method: 'POST',
- body: JSON.stringify({ q: text, target })
- })
- const translatedText = data.translatedText
- if (!translatedText) {
- throw new Error('Translation failed')
- }
- return translatedText
- } catch (error) {
- const errMsg = error instanceof Error ? error.message : ''
- throw new Error(errMsg || 'Failed to translate')
- }
- }
-
- changeCurrentPubkey(pubkey: string | null): void {
- this.currentPubkey = pubkey
- }
-
- private async _fetch({
- path,
- method,
- body,
- auth,
- retryWhenUnauthorized = true
- }: {
- path: string
- method: string
- body?: string
- auth?: string
- retryWhenUnauthorized?: boolean
- }): Promise {
- if (!this.currentPubkey) {
- throw new Error('Please login first')
- }
- const apiKey = this.apiKeyMap[this.currentPubkey]
- const hasApiKey = !!apiKey
- let _auth: string
- if (auth) {
- _auth = auth
- } else if (hasApiKey) {
- _auth = `Bearer ${apiKey}`
- } else {
- const act = await this.getAccount()
- _auth = `Bearer ${act.api_key}`
- }
-
- const url = new URL(path, JUMBLE_API_BASE_URL).toString()
- const response = await fetch(url, {
- method,
- headers: { 'Content-Type': 'application/json', Authorization: _auth },
- body
- })
-
- const data = await response.json()
- if (!response.ok) {
- if (data.code === '00403' && hasApiKey && retryWhenUnauthorized) {
- this.apiKeyMap[this.currentPubkey] = undefined
- return this._fetch({ path, method, body, retryWhenUnauthorized: false })
- }
- throw new Error(data.error)
- }
- return data
- }
-}
-
-const instance = new TranslationService()
-export default instance
diff --git a/src/types/index.d.ts b/src/types/index.d.ts
index 5f819d46..b31900a1 100644
--- a/src/types/index.d.ts
+++ b/src/types/index.d.ts
@@ -186,22 +186,6 @@ export type TEmoji = {
url: string
}
-export type TTranslationAccount = {
- pubkey: string
- api_key: string
- balance: number
-}
-
-export type TTranslationServiceConfig =
- | {
- service: 'jumble'
- }
- | {
- service: 'libre_translate'
- server?: string
- api_key?: string
- }
-
export type TMediaUploadServiceConfig =
| {
type: 'nip96'