diff --git a/src/components/Note/index.tsx b/src/components/Note/index.tsx
index d2831a3..eb2a047 100644
--- a/src/components/Note/index.tsx
+++ b/src/components/Note/index.tsx
@@ -14,6 +14,7 @@ import { FormattedTimestamp } from '../FormattedTimestamp'
import ImageGallery from '../ImageGallery'
import NoteOptions from '../NoteOptions'
import ParentNotePreview from '../ParentNotePreview'
+import TranslateButton from '../TranslateButton'
import UserAvatar from '../UserAvatar'
import Username from '../Username'
import Highlight from './Highlight'
@@ -65,7 +66,10 @@ export default function Note({
- {size === 'normal' && }
+
+
+ {size === 'normal' && }
+
{parentEventId && (
setIsDrawerOpen(true)}
>
diff --git a/src/components/ReplyNote/index.tsx b/src/components/ReplyNote/index.tsx
index cee6b1f..d6732f5 100644
--- a/src/components/ReplyNote/index.tsx
+++ b/src/components/ReplyNote/index.tsx
@@ -14,6 +14,7 @@ import NoteStats from '../NoteStats'
import ParentNotePreview from '../ParentNotePreview'
import UserAvatar from '../UserAvatar'
import Username from '../Username'
+import TranslateButton from '../TranslateButton'
export default function ReplyNote({
event,
@@ -45,7 +46,7 @@ export default function ReplyNote({
-
+
-
+
+
+
+
{parentEventId && (
isSupportedKind(event.kind), [event])
+
+ const needTranslation = useMemo(() => {
+ const cleanText = event.content
+ .replace(URL_REGEX, '')
+ .replace(WS_URL_REGEX, '')
+ .replace(EMAIL_REGEX, '')
+ .replace(EMBEDDED_MENTION_REGEX, '')
+ .replace(EMBEDDED_EVENT_REGEX, '')
+ .replace(HASHTAG_REGEX, '')
+ .replace(EMOJI_REGEX, '')
+ .trim()
+
+ if (!cleanText) {
+ return false
+ }
+
+ const hasChinese = /[\u4e00-\u9fff]/.test(cleanText)
+ const hasJapanese = /[\u3040-\u309f\u30a0-\u30ff]/.test(cleanText)
+ const hasArabic = /[\u0600-\u06ff]/.test(cleanText)
+ const hasRussian = /[\u0400-\u04ff]/.test(cleanText)
+
+ if (hasJapanese) return i18n.language !== 'ja'
+ if (hasChinese && !hasJapanese) return i18n.language !== 'zh'
+
+ if (hasArabic) return i18n.language !== 'ar'
+ if (hasRussian) return i18n.language !== 'ru'
+
+ try {
+ const detectedLang = franc(cleanText)
+ const langMap: { [key: string]: string } = {
+ ara: 'ar', // Arabic
+ deu: 'de', // German
+ eng: 'en', // English
+ spa: 'es', // Spanish
+ fra: 'fr', // French
+ ita: 'it', // Italian
+ jpn: 'ja', // Japanese
+ pol: 'pl', // Polish
+ por: 'pt', // Portuguese
+ rus: 'ru', // Russian
+ cmn: 'zh', // Chinese (Mandarin)
+ zho: 'zh' // Chinese (alternative code)
+ }
+
+ const normalizedLang = langMap[detectedLang]
+ if (!normalizedLang) {
+ return true
+ }
+
+ return !i18n.language.startsWith(normalizedLang)
+ } catch {
+ return true
+ }
+ }, [event, i18n.language])
+
+ if (!supported || !needTranslation) {
+ return null
+ }
+
+ const handleTranslate = async () => {
+ if (translating) return
+
+ setTranslating(true)
+ await translate(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 (
+
+ )
+}
diff --git a/src/constants.ts b/src/constants.ts
index b154cb3..debc353 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -1,3 +1,5 @@
+export const JUMBLE_API_BASE_URL = 'https://api.jumble.social'
+
export const StorageKey = {
VERSION: 'version',
THEME_SETTING: 'themeSetting',
@@ -16,6 +18,7 @@ export const StorageKey = {
AUTOPLAY: 'autoplay',
HIDE_UNTRUSTED_INTERACTIONS: 'hideUntrustedInteractions',
HIDE_UNTRUSTED_NOTIFICATIONS: 'hideUntrustedNotifications',
+ TRANSLATION_SERVICE_CONFIG_MAP: 'translationServiceConfigMap',
HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents', // deprecated
ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated
ACCOUNT_FOLLOW_LIST_EVENT_MAP: 'accountFollowListEventMap', // deprecated
@@ -62,6 +65,8 @@ export const EMBEDDED_EVENT_REGEX = /nostr:(note1[a-z0-9]{58}|nevent1[a-z0-9]+|n
export const EMBEDDED_MENTION_REGEX = /nostr:(npub1[a-z0-9]{58}|nprofile1[a-z0-9]+)/g
export const HASHTAG_REGEX = /#[\p{L}\p{N}\p{M}_]+/gu
export const LN_INVOICE_REGEX = /(ln(?:bc|tb|bcrt))([0-9]+[munp]?)?1([02-9ac-hj-np-z]+)/g
+export const EMOJI_REGEX =
+ /[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F900}-\u{1F9FF}]|[\u{1FA70}-\u{1FAFF}]|[\u{1F004}]|[\u{1F0CF}]|[\u{1F18E}]|[\u{3030}]|[\u{2B50}]|[\u{2B55}]|[\u{2934}-\u{2935}]|[\u{2B05}-\u{2B07}]|[\u{2B1B}-\u{2B1C}]|[\u{3297}]|[\u{3299}]|[\u{303D}]|[\u{00A9}]|[\u{00AE}]|[\u{2122}]|[\u{23E9}-\u{23EF}]|[\u{23F0}]|[\u{23F3}]|[\u{FE00}-\u{FE0F}]|[\u{200D}]/gu
export const MONITOR = '9bbbb845e5b6c831c29789900769843ab43bb5047abe697870cb50b6fc9bf923'
export const MONITOR_RELAYS = ['wss://relay.nostr.watch/']
diff --git a/src/hooks/index.tsx b/src/hooks/index.tsx
index 20948f7..2bf759d 100644
--- a/src/hooks/index.tsx
+++ b/src/hooks/index.tsx
@@ -6,3 +6,4 @@ export * from './useFetchRelayInfo'
export * from './useFetchRelayInfos'
export * from './useFetchRelayList'
export * from './useSearchProfiles'
+export * from './useTranslatedEvent'
diff --git a/src/hooks/useTranslatedEvent.tsx b/src/hooks/useTranslatedEvent.tsx
new file mode 100644
index 0000000..0bcf6b8
--- /dev/null
+++ b/src/hooks/useTranslatedEvent.tsx
@@ -0,0 +1,21 @@
+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/i18n/locales/ar.ts b/src/i18n/locales/ar.ts
index 98ab2d8..e243a1f 100644
--- a/src/i18n/locales/ar.ts
+++ b/src/i18n/locales/ar.ts
@@ -242,6 +242,23 @@ export default {
Quotes: 'الاقتباسات',
'Lightning Invoice': 'فاتورة Lightning',
'Bookmark failed': 'فشل في الإشارة المرجعية',
- 'Remove bookmark failed': 'فشل في إزالة الإشارة المرجعية'
+ 'Remove bookmark failed': 'فشل في إزالة الإشارة المرجعية',
+ Translation: 'الترجمة',
+ Balance: 'الرصيد',
+ characters: 'الحروف',
+ jumbleTranslateApiKeyDescription:
+ 'يمكنك استخدام مفتاح API هذا في أي مكان آخر يدعم LibreTranslate. عنوان الخدمة هو {{serviceUrl}}',
+ 'Top up': 'إعادة شحن',
+ 'Will receive: {n} characters': 'ستتلقى: {{n}} حروف',
+ 'Top up {n} sats': 'إعادة شحن {{n}} ساتوشي',
+ 'Minimum top up is {n} sats': 'الحد الأدنى لإعادة الشحن هو {{n}} ساتوشي',
+ Service: 'الخدمة',
+ 'Reset API key': 'إعادة تعيين مفتاح API',
+ 'Are you sure you want to reset your API key? This action cannot be undone.':
+ 'هل أنت متأكد أنك تريد إعادة تعيين مفتاح API الخاص بك؟ لا يمكن التراجع عن هذا الإجراء.',
+ Warning: 'تحذير',
+ 'Your current API key will become invalid immediately, and any applications using it will stop working until you update them with the new key.':
+ 'مفتاح API الحالي الخاص بك سيصبح غير صالح على الفور، وأي تطبيقات تستخدمه ستتوقف عن العمل حتى تقوم بتحديثها بالمفتاح الجديد.',
+ 'Service address': 'عنوان الخدمة'
}
}
diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts
index 83f0833..30c428f 100644
--- a/src/i18n/locales/de.ts
+++ b/src/i18n/locales/de.ts
@@ -249,6 +249,23 @@ export default {
Quotes: 'Zitate',
'Lightning Invoice': 'Lightning-Rechnung',
'Bookmark failed': 'Bookmark fehlgeschlagen',
- 'Remove bookmark failed': 'Bookmark entfernen fehlgeschlagen'
+ 'Remove bookmark failed': 'Bookmark entfernen fehlgeschlagen',
+ Translation: 'Übersetzung',
+ Balance: 'Guthaben',
+ characters: 'Zeichen',
+ jumbleTranslateApiKeyDescription:
+ 'Du kannst diesen API-Schlüssel überall dort verwenden, wo LibreTranslate unterstützt wird. Die Service-URL ist {{serviceUrl}}',
+ 'Top up': 'Aufladen',
+ 'Will receive: {n} characters': 'Erhalte: {{n}} Zeichen',
+ 'Top up {n} sats': 'Lade {{n}} sats auf',
+ 'Minimum top up is {n} sats': 'Minimale Aufladung beträgt {{n}} sats',
+ Service: 'Dienst',
+ 'Reset API key': 'API-Schlüssel zurücksetzen',
+ 'Are you sure you want to reset your API key? This action cannot be undone.':
+ 'Bist du sicher, dass du deinen API-Schlüssel zurücksetzen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.',
+ Warning: 'Warnung',
+ 'Your current API key will become invalid immediately, and any applications using it will stop working until you update them with the new key.':
+ 'Dein aktueller API-Schlüssel wird sofort ungültig, und alle Anwendungen, die ihn verwenden, werden nicht mehr funktionieren, bis du sie mit dem neuen Schlüssel aktualisierst.',
+ 'Service address': 'Service-Adresse'
}
}
diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts
index ceebf17..935337a 100644
--- a/src/i18n/locales/en.ts
+++ b/src/i18n/locales/en.ts
@@ -242,6 +242,23 @@ export default {
Quotes: 'Quotes',
'Lightning Invoice': 'Lightning Invoice',
'Bookmark failed': 'Bookmark failed',
- 'Remove bookmark failed': 'Remove bookmark failed'
+ 'Remove bookmark failed': 'Remove bookmark failed',
+ Translation: 'Translation',
+ Balance: 'Balance',
+ characters: 'characters',
+ jumbleTranslateApiKeyDescription:
+ 'You can use this API key anywhere else that supports LibreTranslate. The service URL is {{serviceUrl}}',
+ 'Top up': 'Top up',
+ 'Will receive: {n} characters': 'Will receive: {{n}} characters',
+ 'Top up {n} sats': 'Top up {{n}} sats',
+ 'Minimum top up is {n} sats': 'Minimum top up is {{n}} sats',
+ Service: 'Service',
+ 'Reset API key': 'Reset API key',
+ 'Are you sure you want to reset your API key? This action cannot be undone.':
+ 'Are you sure you want to reset your API key? This action cannot be undone.',
+ Warning: 'Warning',
+ 'Your current API key will become invalid immediately, and any applications using it will stop working until you update them with the new key.':
+ 'Your current API key will become invalid immediately, and any applications using it will stop working until you update them with the new key.',
+ 'Service address': 'Service address'
}
}
diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts
index 7fa3ca9..b871590 100644
--- a/src/i18n/locales/es.ts
+++ b/src/i18n/locales/es.ts
@@ -247,6 +247,23 @@ export default {
Quotes: 'Citas',
'Lightning Invoice': 'Factura Lightning',
'Bookmark failed': 'Error al marcar',
- 'Remove bookmark failed': 'Error al quitar marcador'
+ 'Remove bookmark failed': 'Error al quitar marcador',
+ Translation: 'Traducción',
+ Balance: 'Saldo',
+ characters: 'caracteres',
+ jumbleTranslateApiKeyDescription:
+ 'Puedes usar esta clave API en cualquier otro lugar que soporte LibreTranslate. La URL del servicio es {{serviceUrl}}',
+ 'Top up': 'Recargar',
+ 'Will receive: {n} characters': 'Recibirás: {{n}} caracteres',
+ 'Top up {n} sats': 'Recargar {{n}} satoshis',
+ 'Minimum top up is {n} sats': 'La recarga mínima es de {{n}} satoshis',
+ Service: 'Servicio',
+ 'Reset API key': 'Restablecer clave API',
+ 'Are you sure you want to reset your API key? This action cannot be undone.':
+ '¿Estás seguro de que deseas restablecer tu clave API? Esta acción no se puede deshacer.',
+ Warning: 'Advertencia',
+ 'Your current API key will become invalid immediately, and any applications using it will stop working until you update them with the new key.':
+ 'Tu clave API actual se volverá inválida de inmediato, y cualquier aplicación que la use dejará de funcionar hasta que las actualices con la nueva clave.',
+ 'Service address': 'Dirección del servicio'
}
}
diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts
index 8da7a2d..6d70977 100644
--- a/src/i18n/locales/fr.ts
+++ b/src/i18n/locales/fr.ts
@@ -247,6 +247,23 @@ export default {
Quotes: 'Citations',
'Lightning Invoice': 'Facture Lightning',
'Bookmark failed': 'Échec de la mise en favori',
- 'Remove bookmark failed': 'Échec de la suppression du favori'
+ 'Remove bookmark failed': 'Échec de la suppression du favori',
+ Translation: 'Traduction',
+ Balance: 'Solde',
+ characters: 'caractères',
+ jumbleTranslateApiKeyDescription:
+ 'Vous pouvez utiliser cette clé API ailleurs qui prend en charge LibreTranslate. L’URL du service est {{serviceUrl}}',
+ 'Top up': 'Recharger',
+ 'Will receive: {n} characters': 'Vous recevrez : {{n}} caractères',
+ 'Top up {n} sats': 'Recharger {{n}} sats',
+ 'Minimum top up is {n} sats': 'Le rechargement minimum est de {{n}} sats',
+ Service: 'Service',
+ 'Reset API key': 'Réinitialiser la clé API',
+ 'Are you sure you want to reset your API key? This action cannot be undone.':
+ 'Êtes-vous sûr de vouloir réinitialiser votre clé API ? Cette action ne peut pas être annulée.',
+ Warning: 'Avertissement',
+ 'Your current API key will become invalid immediately, and any applications using it will stop working until you update them with the new key.':
+ 'Votre clé API actuelle deviendra immédiatement invalide, et toutes les applications qui l’utilisent cesseront de fonctionner jusqu’à ce que vous les mettiez à jour avec la nouvelle clé.',
+ 'Service address': 'Adresse du service'
}
}
diff --git a/src/i18n/locales/it.ts b/src/i18n/locales/it.ts
index cd9e800..20a3378 100644
--- a/src/i18n/locales/it.ts
+++ b/src/i18n/locales/it.ts
@@ -246,6 +246,23 @@ export default {
Quotes: 'Citazioni',
'Lightning Invoice': 'Fattura Lightning',
'Bookmark failed': 'Impossibile aggiungere segnalibro',
- 'Remove bookmark failed': 'Impossibile rimuovere segnalibro'
+ 'Remove bookmark failed': 'Impossibile rimuovere segnalibro',
+ Translation: 'Traduzione',
+ Balance: 'Saldo',
+ characters: 'caratteri',
+ jumbleTranslateApiKeyDescription:
+ "Puoi utilizzare questa chiave API ovunque supporti LibreTranslate. L'URL del servizio è {{serviceUrl}}",
+ 'Top up': 'Torna al saldo',
+ 'Will receive: {n} characters': 'Riceverai: {{n}} caratteri',
+ 'Top up {n} sats': 'Ricarica {{n}} sats',
+ 'Minimum top up is {n} sats': 'La ricarica minima è di {{n}} sats',
+ Service: 'Servizio',
+ 'Reset API key': 'Reimposta chiave API',
+ 'Are you sure you want to reset your API key? This action cannot be undone.':
+ 'Sei sicuro di voler reimpostare la tua chiave API? Questa azione non può essere annullata.',
+ Warning: 'Attenzione',
+ 'Your current API key will become invalid immediately, and any applications using it will stop working until you update them with the new key.':
+ 'La tua attuale chiave API diventerà immediatamente non valida e tutte le applicazioni che la utilizzano smetteranno di funzionare finché non le aggiornerai con la nuova chiave.',
+ 'Service address': 'Indirizzo del servizio'
}
}
diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts
index 82ba70f..688f012 100644
--- a/src/i18n/locales/ja.ts
+++ b/src/i18n/locales/ja.ts
@@ -243,6 +243,23 @@ export default {
Quotes: '引用',
'Lightning Invoice': 'ライトニングインボイス',
'Bookmark failed': 'ブックマークに失敗しました',
- 'Remove bookmark failed': 'ブックマークの削除に失敗しました'
+ 'Remove bookmark failed': 'ブックマークの削除に失敗しました',
+ Translation: '翻訳',
+ Balance: '残高',
+ characters: '文字',
+ jumbleTranslateApiKeyDescription:
+ 'このAPIキーは、LibreTranslateをサポートする他の場所でも使用できます。サービスURLは{{serviceUrl}}です',
+ 'Top up': 'チャージ',
+ 'Will receive: {n} characters': '受け取る文字数: {{n}} 文字',
+ 'Top up {n} sats': 'チャージ {{n}} サッツ',
+ 'Minimum top up is {n} sats': '最低チャージは {{n}} サッツです',
+ Service: 'サービス',
+ 'Reset API key': 'APIキーをリセット',
+ 'Are you sure you want to reset your API key? This action cannot be undone.':
+ 'APIキーをリセットしますか?この操作は元に戻せません。',
+ Warning: '警告',
+ 'Your current API key will become invalid immediately, and any applications using it will stop working until you update them with the new key.':
+ '現在のAPIキーはすぐに無効になり、それを使用しているアプリケーションは新しいキーで更新するまで動作しなくなります。',
+ 'Service address': 'サービスアドレス'
}
}
diff --git a/src/i18n/locales/pl.ts b/src/i18n/locales/pl.ts
index e521c8f..37b7f5a 100644
--- a/src/i18n/locales/pl.ts
+++ b/src/i18n/locales/pl.ts
@@ -245,6 +245,23 @@ export default {
Quotes: 'Cytaty',
'Lightning Invoice': 'Faktura Lightning',
'Bookmark failed': 'Nie udało się dodać zakładki',
- 'Remove bookmark failed': 'Nie udało się usunąć zakładki'
+ 'Remove bookmark failed': 'Nie udało się usunąć zakładki',
+ Translation: 'Tłumaczenie',
+ Balance: 'Saldo',
+ characters: 'znaków',
+ jumbleTranslateApiKeyDescription:
+ 'Ten klucz API możesz używać wszędzie tam, gdzie obsługiwane jest LibreTranslate. Adres usługi to {{serviceUrl}}',
+ 'Top up': 'Doładuj',
+ 'Will receive: {n} characters': 'Otrzymasz: {{n}} znaków',
+ 'Top up {n} sats': 'Doładuj {{n}} satsów',
+ 'Minimum top up is {n} sats': 'Minimalne doładowanie to {{n}} satsów',
+ Service: 'Usługa',
+ 'Reset API key': 'Zresetuj klucz API',
+ 'Are you sure you want to reset your API key? This action cannot be undone.':
+ 'Czy na pewno chcesz zresetować swój klucz API? Ta akcja jest nieodwracalna.',
+ Warning: 'Ostrzeżenie',
+ 'Your current API key will become invalid immediately, and any applications using it will stop working until you update them with the new key.':
+ 'Twój obecny klucz API stanie się nieaktywny natychmiast, a wszystkie aplikacje korzystające z niego przestaną działać, dopóki nie zaktualizujesz ich nowym kluczem.',
+ 'Service address': 'Adres usługi'
}
}
diff --git a/src/i18n/locales/pt-BR.ts b/src/i18n/locales/pt-BR.ts
index d9a517a..bd7ff87 100644
--- a/src/i18n/locales/pt-BR.ts
+++ b/src/i18n/locales/pt-BR.ts
@@ -245,6 +245,23 @@ export default {
Quotes: 'Citações',
'Lightning Invoice': 'Fatura Lightning',
'Bookmark failed': 'Falha ao favoritar',
- 'Remove bookmark failed': 'Falha ao remover favorito'
+ 'Remove bookmark failed': 'Falha ao remover favorito',
+ Translation: 'Tradução',
+ Balance: 'Saldo',
+ characters: 'caracteres',
+ jumbleTranslateApiKeyDescription:
+ 'Esta chave API pode ser usada em qualquer outro lugar que suporte LibreTranslate. O URL do serviço é {{serviceUrl}}',
+ 'Top up': 'Carregar saldo',
+ 'Will receive: {n} characters': 'Receberá: {{n}} caracteres',
+ 'Top up {n} sats': 'Carregar {{n}} sats',
+ 'Minimum top up is {n} sats': 'Carregamento mínimo é {{n}} sats',
+ Service: 'Serviço',
+ 'Reset API key': 'Redefinir chave API',
+ 'Are you sure you want to reset your API key? This action cannot be undone.':
+ 'Tem certeza de que deseja redefinir sua chave API? Esta ação não pode ser desfeita.',
+ Warning: 'Aviso',
+ 'Your current API key will become invalid immediately, and any applications using it will stop working until you update them with the new key.':
+ 'Sua chave API atual se tornará inválida imediatamente, e qualquer aplicativo que a utilize deixará de funcionar até que você os atualize com a nova chave.',
+ 'Service address': 'Endereço do serviço'
}
}
diff --git a/src/i18n/locales/pt-PT.ts b/src/i18n/locales/pt-PT.ts
index 8d9b1ea..1c53c61 100644
--- a/src/i18n/locales/pt-PT.ts
+++ b/src/i18n/locales/pt-PT.ts
@@ -246,6 +246,23 @@ export default {
Quotes: 'Citações',
'Lightning Invoice': 'Fatura Lightning',
'Bookmark failed': 'Falha ao favoritar',
- 'Remove bookmark failed': 'Falha ao remover favorito'
+ 'Remove bookmark failed': 'Falha ao remover favorito',
+ Translation: 'Tradução',
+ Balance: 'Saldo',
+ characters: 'caracteres',
+ jumbleTranslateApiKeyDescription:
+ 'Esta chave API pode ser usada em qualquer outro lugar que suporte LibreTranslate. O URL do serviço é {{serviceUrl}}',
+ 'Top up': 'Carregar',
+ 'Will receive: {n} characters': 'Receberá: {{n}} caracteres',
+ 'Top up {n} sats': 'Carregar {{n}} sats',
+ 'Minimum top up is {n} sats': 'O carregamento mínimo é de {{n}} sats',
+ Service: 'Serviço',
+ 'Reset API key': 'Redefinir chave API',
+ 'Are you sure you want to reset your API key? This action cannot be undone.':
+ 'Tem certeza de que deseja redefinir sua chave API? Esta ação não pode ser desfeita.',
+ Warning: 'Aviso',
+ 'Your current API key will become invalid immediately, and any applications using it will stop working until you update them with the new key.':
+ 'Sua chave API atual se tornará inválida imediatamente, e qualquer aplicativo que a utilize deixará de funcionar até que você os atualize com a nova chave.',
+ 'Service address': 'Endereço do serviço'
}
}
diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts
index c060863..0736073 100644
--- a/src/i18n/locales/ru.ts
+++ b/src/i18n/locales/ru.ts
@@ -247,6 +247,23 @@ export default {
Quotes: 'Цитаты',
'Lightning Invoice': 'Lightning-счет',
'Bookmark failed': 'Не удалось добавить закладку',
- 'Remove bookmark failed': 'Не удалось удалить закладку'
+ 'Remove bookmark failed': 'Не удалось удалить закладку',
+ Translation: 'Перевод',
+ Balance: 'Баланс',
+ characters: 'символов',
+ jumbleTranslateApiKeyDescription:
+ 'Вы можете использовать этот API-ключ в любом другом месте, которое поддерживает LibreTranslate. URL сервиса: {{serviceUrl}}',
+ 'Top up': 'Пополнить',
+ 'Will receive: {n} characters': 'Получите: {{n}} символов',
+ 'Top up {n} sats': 'Пополнить на {{n}} сатс',
+ 'Minimum top up is {n} sats': 'Минимальное пополнение составляет {{n}} сатс',
+ Service: 'Сервис',
+ 'Reset API key': 'Сбросить API-ключ',
+ 'Are you sure you want to reset your API key? This action cannot be undone.':
+ 'Вы уверены, что хотите сбросить ваш API-ключ? Это действие не может быть отменено.',
+ Warning: 'Предупреждение',
+ 'Your current API key will become invalid immediately, and any applications using it will stop working until you update them with the new key.':
+ 'Ваш текущий API-ключ станет недействительным немедленно, и любые приложения, использующие его, перестанут работать, пока вы не обновите их новым ключом.',
+ 'Service address': 'Адрес сервиса'
}
}
diff --git a/src/i18n/locales/zh.ts b/src/i18n/locales/zh.ts
index d6ea4e9..b06a5f9 100644
--- a/src/i18n/locales/zh.ts
+++ b/src/i18n/locales/zh.ts
@@ -243,6 +243,23 @@ export default {
Quotes: '引用',
'Lightning Invoice': '闪电发票',
'Bookmark failed': '收藏失败',
- 'Remove bookmark failed': '取消收藏失败'
+ 'Remove bookmark failed': '取消收藏失败',
+ Translation: '翻译',
+ Balance: '余额',
+ characters: '字符',
+ jumbleTranslateApiKeyDescription:
+ '您可以在任何支持 LibreTranslate 的地方使用此 API key。服务地址是 {{serviceUrl}}',
+ 'Top up': '充值',
+ 'Will receive: {n} characters': '将获得: {{n}} 字符',
+ 'Top up {n} sats': '充值 {{n}} 聪',
+ 'Minimum top up is {n} sats': '最低充值金额为 {{n}} 聪',
+ Service: '服务',
+ 'Reset API key': '重置 API key',
+ 'Are you sure you want to reset your API key? This action cannot be undone.':
+ '您确定要重置您的 API key?此操作无法撤销。',
+ Warning: '警告',
+ 'Your current API key will become invalid immediately, and any applications using it will stop working until you update them with the new key.':
+ '您当前的 API key 将立即失效,任何使用它的应用程序将停止工作,直到您用新 key 更新它们。',
+ 'Service address': '服务地址'
}
}
diff --git a/src/lib/link.ts b/src/lib/link.ts
index 87d549e..1984a92 100644
--- a/src/lib/link.ts
+++ b/src/lib/link.ts
@@ -50,6 +50,7 @@ export const toRelaySettings = (tag?: 'mailbox' | 'favorite-relays') => {
export const toWallet = () => '/settings/wallet'
export const toPostSettings = () => '/settings/posts'
export const toGeneralSettings = () => '/settings/general'
+export const toTranslation = () => '/settings/translation'
export const toProfileEditor = () => '/profile-editor'
export const toRelay = (url: string) => `/relays/${encodeURIComponent(url)}`
export const toMuteList = () => '/mutes'
diff --git a/src/pages/secondary/SettingsPage/index.tsx b/src/pages/secondary/SettingsPage/index.tsx
index 2f6f2ee..958670d 100644
--- a/src/pages/secondary/SettingsPage/index.tsx
+++ b/src/pages/secondary/SettingsPage/index.tsx
@@ -1,7 +1,13 @@
import AboutInfoDialog from '@/components/AboutInfoDialog'
import Donation from '@/components/Donation'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
-import { toGeneralSettings, toPostSettings, toRelaySettings, toWallet } from '@/lib/link'
+import {
+ toGeneralSettings,
+ toPostSettings,
+ toRelaySettings,
+ toTranslation,
+ toWallet
+} from '@/lib/link'
import { cn } from '@/lib/utils'
import { useSecondaryPage } from '@/PageManager'
import { useNostr } from '@/providers/NostrProvider'
@@ -11,6 +17,7 @@ import {
Copy,
Info,
KeyRound,
+ Languages,
PencilLine,
Server,
Settings2,
@@ -42,6 +49,13 @@ const SettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
+ push(toTranslation())}>
+
+
+
{t('Translation')}
+
+
+ push(toWallet())}>
diff --git a/src/pages/secondary/TranslationPage/JumbleTranslate/AccountInfo.tsx b/src/pages/secondary/TranslationPage/JumbleTranslate/AccountInfo.tsx
new file mode 100644
index 0000000..b733baa
--- /dev/null
+++ b/src/pages/secondary/TranslationPage/JumbleTranslate/AccountInfo.tsx
@@ -0,0 +1,75 @@
+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 (
+
+
+
+ )
+ }
+
+ return (
+
+ {/* Balance display in characters */}
+
+
{t('Balance')}
+
+
{account?.balance.toLocaleString() ?? '0'}
+
{t('characters')}
+
+
+
+ {/* API Key section with visibility toggle and copy functionality */}
+