feat: add relay recommendations based on user language

This commit is contained in:
codytseng
2025-12-23 22:28:07 +08:00
parent a880a92748
commit 0ee93718da
21 changed files with 92 additions and 29 deletions

View File

@@ -1,22 +1,30 @@
import { Skeleton } from '@/components/ui/skeleton' import { Skeleton } from '@/components/ui/skeleton'
import { useFetchRelayInfo } from '@/hooks' import { useFetchRelayInfo } from '@/hooks'
import { toRelay } from '@/lib/link' import { toRelay } from '@/lib/link'
import { recommendRelaysByLanguage } from '@/lib/relay'
import { cn } from '@/lib/utils'
import { useSecondaryPage } from '@/PageManager' import { useSecondaryPage } from '@/PageManager'
import { useDeepBrowsing } from '@/providers/DeepBrowsingProvider'
import relayInfoService from '@/services/relay-info.service' import relayInfoService from '@/services/relay-info.service'
import { TAwesomeRelayCollection } from '@/types' import { TAwesomeRelayCollection } from '@/types'
import { useEffect, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import RelaySimpleInfo, { RelaySimpleInfoSkeleton } from '../RelaySimpleInfo' import RelaySimpleInfo, { RelaySimpleInfoSkeleton } from '../RelaySimpleInfo'
import { useDeepBrowsing } from '@/providers/DeepBrowsingProvider'
import { cn } from '@/lib/utils'
export default function Explore() { export default function Explore() {
const { t, i18n } = useTranslation()
const [collections, setCollections] = useState<TAwesomeRelayCollection[] | null>(null) const [collections, setCollections] = useState<TAwesomeRelayCollection[] | null>(null)
const recommendedRelays = useMemo(() => {
const lang = i18n.language
const relays = recommendRelaysByLanguage(lang)
return relays
}, [i18n.language])
useEffect(() => { useEffect(() => {
relayInfoService.getAwesomeRelayCollections().then(setCollections) relayInfoService.getAwesomeRelayCollections().then(setCollections)
}, []) }, [])
if (!collections) { if (!collections && recommendedRelays.length === 0) {
return ( return (
<div> <div>
<div className="p-4 max-md:border-b"> <div className="p-4 max-md:border-b">
@@ -31,7 +39,17 @@ export default function Explore() {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{collections.map((collection) => ( {recommendedRelays.length > 0 && (
<RelayCollection
collection={{
id: 'recommended',
name: t('Recommended'),
relays: recommendedRelays
}}
/>
)}
{collections &&
collections.map((collection) => (
<RelayCollection key={collection.id} collection={collection} /> <RelayCollection key={collection.id} collection={collection} />
))} ))}
</div> </div>

View File

@@ -633,6 +633,7 @@ export default {
'أضف كلمة مرور لتشفير مفتاحك الخاص في هذا المتصفح. هذا اختياري لكنه موصى به بشدة لأمان أفضل.', 'أضف كلمة مرور لتشفير مفتاحك الخاص في هذا المتصفح. هذا اختياري لكنه موصى به بشدة لأمان أفضل.',
'Create a password (or skip)': 'أنشئ كلمة مرور (أو تخطى)', 'Create a password (or skip)': 'أنشئ كلمة مرور (أو تخطى)',
'Enter your password again': 'أدخل كلمة المرور مرة أخرى', 'Enter your password again': 'أدخل كلمة المرور مرة أخرى',
'Complete Signup': 'إكمال التسجيل' 'Complete Signup': 'إكمال التسجيل',
Recommended: 'موصى به'
} }
} }

View File

@@ -654,6 +654,7 @@ export default {
'Fügen Sie ein Passwort hinzu, um Ihren privaten Schlüssel in diesem Browser zu verschlüsseln. Dies ist optional, aber für bessere Sicherheit dringend empfohlen.', 'Fügen Sie ein Passwort hinzu, um Ihren privaten Schlüssel in diesem Browser zu verschlüsseln. Dies ist optional, aber für bessere Sicherheit dringend empfohlen.',
'Create a password (or skip)': 'Erstellen Sie ein Passwort (oder überspringen)', 'Create a password (or skip)': 'Erstellen Sie ein Passwort (oder überspringen)',
'Enter your password again': 'Geben Sie Ihr Passwort erneut ein', 'Enter your password again': 'Geben Sie Ihr Passwort erneut ein',
'Complete Signup': 'Registrierung abschließen' 'Complete Signup': 'Registrierung abschließen',
Recommended: 'Empfohlen'
} }
} }

View File

@@ -638,6 +638,7 @@ export default {
'Add a password to encrypt your private key in this browser. This is optional but strongly recommended for better security.', 'Add a password to encrypt your private key in this browser. This is optional but strongly recommended for better security.',
'Create a password (or skip)': 'Create a password (or skip)', 'Create a password (or skip)': 'Create a password (or skip)',
'Enter your password again': 'Enter your password again', 'Enter your password again': 'Enter your password again',
'Complete Signup': 'Complete Signup' 'Complete Signup': 'Complete Signup',
Recommended: 'Recommended'
} }
} }

View File

@@ -648,6 +648,7 @@ export default {
'Añade una contraseña para cifrar tu clave privada en este navegador. Esto es opcional pero muy recomendado para mayor seguridad.', 'Añade una contraseña para cifrar tu clave privada en este navegador. Esto es opcional pero muy recomendado para mayor seguridad.',
'Create a password (or skip)': 'Crear una contraseña (o saltar)', 'Create a password (or skip)': 'Crear una contraseña (o saltar)',
'Enter your password again': 'Ingresa tu contraseña nuevamente', 'Enter your password again': 'Ingresa tu contraseña nuevamente',
'Complete Signup': 'Completar registro' 'Complete Signup': 'Completar registro',
Recommended: 'Recomendado'
} }
} }

View File

@@ -643,6 +643,7 @@ export default {
'یک رمز عبور برای رمزگذاری کلید خصوصی خود در این مرورگر اضافه کنید. این اختیاری است اما برای امنیت بهتر به شدت توصیه می‌شود.', 'یک رمز عبور برای رمزگذاری کلید خصوصی خود در این مرورگر اضافه کنید. این اختیاری است اما برای امنیت بهتر به شدت توصیه می‌شود.',
'Create a password (or skip)': 'یک رمز عبور ایجاد کنید (یا رد کنید)', 'Create a password (or skip)': 'یک رمز عبور ایجاد کنید (یا رد کنید)',
'Enter your password again': 'رمز عبور خود را دوباره وارد کنید', 'Enter your password again': 'رمز عبور خود را دوباره وارد کنید',
'Complete Signup': 'تکمیل ثبت‌نام' 'Complete Signup': 'تکمیل ثبت‌نام',
Recommended: 'توصیه شده'
} }
} }

View File

@@ -651,6 +651,7 @@ export default {
"Ajoutez un mot de passe pour chiffrer votre clé privée dans ce navigateur. C'est facultatif mais fortement recommandé pour une meilleure sécurité.", "Ajoutez un mot de passe pour chiffrer votre clé privée dans ce navigateur. C'est facultatif mais fortement recommandé pour une meilleure sécurité.",
'Create a password (or skip)': 'Créez un mot de passe (ou ignorez)', 'Create a password (or skip)': 'Créez un mot de passe (ou ignorez)',
'Enter your password again': 'Entrez à nouveau votre mot de passe', 'Enter your password again': 'Entrez à nouveau votre mot de passe',
'Complete Signup': "Terminer l'inscription" 'Complete Signup': "Terminer l'inscription",
Recommended: 'Recommandé'
} }
} }

View File

@@ -644,6 +644,7 @@ export default {
'इस ब्राउज़र में अपनी निजी कुंजी को एन्क्रिप्ट करने के लिए पासवर्ड जोड़ें। यह वैकल्पिक है लेकिन बेहतर सुरक्षा के लिए दृढ़ता से अनुशंसित है।', 'इस ब्राउज़र में अपनी निजी कुंजी को एन्क्रिप्ट करने के लिए पासवर्ड जोड़ें। यह वैकल्पिक है लेकिन बेहतर सुरक्षा के लिए दृढ़ता से अनुशंसित है।',
'Create a password (or skip)': 'एक पासवर्ड बनाएं (या छोड़ें)', 'Create a password (or skip)': 'एक पासवर्ड बनाएं (या छोड़ें)',
'Enter your password again': 'अपना पासवर्ड फिर से दर्ज करें', 'Enter your password again': 'अपना पासवर्ड फिर से दर्ज करें',
'Complete Signup': 'साइनअप पूर्ण करें' 'Complete Signup': 'साइनअप पूर्ण करें',
Recommended: 'अनुशंसित'
} }
} }

View File

@@ -636,6 +636,7 @@ export default {
'Adj hozzá jelszót a privát kulcsod titkosításához ebben a böngészőben. Ez opcionális, de erősen ajánlott a jobb biztonság érdekében.', 'Adj hozzá jelszót a privát kulcsod titkosításához ebben a böngészőben. Ez opcionális, de erősen ajánlott a jobb biztonság érdekében.',
'Create a password (or skip)': 'Hozz létre jelszót (vagy hagyd ki)', 'Create a password (or skip)': 'Hozz létre jelszót (vagy hagyd ki)',
'Enter your password again': 'Add meg újra a jelszavad', 'Enter your password again': 'Add meg újra a jelszavad',
'Complete Signup': 'Regisztráció befejezése' 'Complete Signup': 'Regisztráció befejezése',
Recommended: 'Ajánlott'
} }
} }

View File

@@ -648,6 +648,7 @@ export default {
'Aggiungi una password per crittografare la tua chiave privata in questo browser. È facoltativo ma fortemente consigliato per una migliore sicurezza.', 'Aggiungi una password per crittografare la tua chiave privata in questo browser. È facoltativo ma fortemente consigliato per una migliore sicurezza.',
'Create a password (or skip)': 'Crea una password (o salta)', 'Create a password (or skip)': 'Crea una password (o salta)',
'Enter your password again': 'Inserisci di nuovo la tua password', 'Enter your password again': 'Inserisci di nuovo la tua password',
'Complete Signup': 'Completa registrazione' 'Complete Signup': 'Completa registrazione',
Recommended: 'Consigliato'
} }
} }

View File

@@ -642,6 +642,7 @@ export default {
'このブラウザで秘密鍵を暗号化するパスワードを追加します。オプションですが、より良いセキュリティのために強くお勧めします。', 'このブラウザで秘密鍵を暗号化するパスワードを追加します。オプションですが、より良いセキュリティのために強くお勧めします。',
'Create a password (or skip)': 'パスワードを作成(またはスキップ)', 'Create a password (or skip)': 'パスワードを作成(またはスキップ)',
'Enter your password again': 'パスワードをもう一度入力', 'Enter your password again': 'パスワードをもう一度入力',
'Complete Signup': '登録を完了' 'Complete Signup': '登録を完了',
Recommended: 'おすすめ'
} }
} }

View File

@@ -639,6 +639,7 @@ export default {
'이 브라우저에서 개인 키를 암호화할 비밀번호를 추가합니다. 선택사항이지만 더 나은 보안을 위해 강력히 권장합니다.', '이 브라우저에서 개인 키를 암호화할 비밀번호를 추가합니다. 선택사항이지만 더 나은 보안을 위해 강력히 권장합니다.',
'Create a password (or skip)': '비밀번호 생성(또는 건너뛰기)', 'Create a password (or skip)': '비밀번호 생성(또는 건너뛰기)',
'Enter your password again': '비밀번호를 다시 입력하세요', 'Enter your password again': '비밀번호를 다시 입력하세요',
'Complete Signup': '가입 완료' 'Complete Signup': '가입 완료',
Recommended: '추천'
} }
} }

View File

@@ -649,6 +649,7 @@ export default {
'Dodaj hasło, aby zaszyfrować swój klucz prywatny w tej przeglądarce. Jest to opcjonalne, ale zdecydowanie zalecane dla lepszego bezpieczeństwa.', 'Dodaj hasło, aby zaszyfrować swój klucz prywatny w tej przeglądarce. Jest to opcjonalne, ale zdecydowanie zalecane dla lepszego bezpieczeństwa.',
'Create a password (or skip)': 'Utwórz hasło (lub pomiń)', 'Create a password (or skip)': 'Utwórz hasło (lub pomiń)',
'Enter your password again': 'Wprowadź hasło ponownie', 'Enter your password again': 'Wprowadź hasło ponownie',
'Complete Signup': 'Zakończ rejestrację' 'Complete Signup': 'Zakończ rejestrację',
Recommended: 'Polecane'
} }
} }

View File

@@ -644,6 +644,7 @@ export default {
'Adicione uma senha para criptografar sua chave privada neste navegador. Isso é opcional, mas fortemente recomendado para melhor segurança.', 'Adicione uma senha para criptografar sua chave privada neste navegador. Isso é opcional, mas fortemente recomendado para melhor segurança.',
'Create a password (or skip)': 'Crie uma senha (ou pule)', 'Create a password (or skip)': 'Crie uma senha (ou pule)',
'Enter your password again': 'Digite sua senha novamente', 'Enter your password again': 'Digite sua senha novamente',
'Complete Signup': 'Concluir cadastro' 'Complete Signup': 'Concluir cadastro',
Recommended: 'Recomendado'
} }
} }

View File

@@ -647,6 +647,7 @@ export default {
'Adicione uma palavra-passe para encriptar a sua chave privada neste navegador. Isto é opcional, mas fortemente recomendado para melhor segurança.', 'Adicione uma palavra-passe para encriptar a sua chave privada neste navegador. Isto é opcional, mas fortemente recomendado para melhor segurança.',
'Create a password (or skip)': 'Crie uma palavra-passe (ou ignore)', 'Create a password (or skip)': 'Crie uma palavra-passe (ou ignore)',
'Enter your password again': 'Introduza novamente a sua palavra-passe', 'Enter your password again': 'Introduza novamente a sua palavra-passe',
'Complete Signup': 'Concluir registo' 'Complete Signup': 'Concluir registo',
Recommended: 'Recomendado'
} }
} }

View File

@@ -648,6 +648,7 @@ export default {
'Добавьте пароль для шифрования вашего приватного ключа в этом браузере. Это необязательно, но настоятельно рекомендуется для лучшей безопасности.', 'Добавьте пароль для шифрования вашего приватного ключа в этом браузере. Это необязательно, но настоятельно рекомендуется для лучшей безопасности.',
'Create a password (or skip)': 'Создайте пароль (или пропустите)', 'Create a password (or skip)': 'Создайте пароль (или пропустите)',
'Enter your password again': 'Введите пароль еще раз', 'Enter your password again': 'Введите пароль еще раз',
'Complete Signup': 'Завершить регистрацию' 'Complete Signup': 'Завершить регистрацию',
Recommended: 'Рекомендуемые'
} }
} }

View File

@@ -633,6 +633,7 @@ export default {
'เพิ่มรหัสผ่านเพื่อเข้ารหัสคีย์ส่วนตัวของคุณในเบราว์เซอร์นี้ เป็นตัวเลือก แต่แนะนำอย่างยิ่งเพื่อความปลอดภัยที่ดีขึ้น', 'เพิ่มรหัสผ่านเพื่อเข้ารหัสคีย์ส่วนตัวของคุณในเบราว์เซอร์นี้ เป็นตัวเลือก แต่แนะนำอย่างยิ่งเพื่อความปลอดภัยที่ดีขึ้น',
'Create a password (or skip)': 'สร้างรหัสผ่าน (หรือข้าม)', 'Create a password (or skip)': 'สร้างรหัสผ่าน (หรือข้าม)',
'Enter your password again': 'ป้อนรหัสผ่านของคุณอีกครั้ง', 'Enter your password again': 'ป้อนรหัสผ่านของคุณอีกครั้ง',
'Complete Signup': 'เสร็จสิ้นการลงทะเบียน' 'Complete Signup': 'เสร็จสิ้นการลงทะเบียน',
Recommended: 'แนะนำ'
} }
} }

View File

@@ -619,6 +619,7 @@ export default {
'新增密碼以在此瀏覽器中加密你的私鑰。這是可選的,但強烈建議設定以獲得更好的安全性。', '新增密碼以在此瀏覽器中加密你的私鑰。這是可選的,但強烈建議設定以獲得更好的安全性。',
'Create a password (or skip)': '建立密碼(或跳過)', 'Create a password (or skip)': '建立密碼(或跳過)',
'Enter your password again': '再次輸入你的密碼', 'Enter your password again': '再次輸入你的密碼',
'Complete Signup': '完成註冊' 'Complete Signup': '完成註冊',
Recommended: '推薦'
} }
} }

View File

@@ -624,6 +624,7 @@ export default {
'添加密码以在此浏览器中加密你的私钥。这是可选的,但强烈建议设置以获得更好的安全性。', '添加密码以在此浏览器中加密你的私钥。这是可选的,但强烈建议设置以获得更好的安全性。',
'Create a password (or skip)': '创建密码(或跳过)', 'Create a password (or skip)': '创建密码(或跳过)',
'Enter your password again': '再次输入你的密码', 'Enter your password again': '再次输入你的密码',
'Complete Signup': '完成注册' 'Complete Signup': '完成注册',
Recommended: '推荐'
} }
} }

View File

@@ -16,3 +16,27 @@ export function checkNip43Support(relayInfo: TRelayInfo | undefined) {
export function filterOutBigRelays(relayUrls: string[]) { export function filterOutBigRelays(relayUrls: string[]) {
return relayUrls.filter((url) => !BIG_RELAY_URLS.includes(url)) return relayUrls.filter((url) => !BIG_RELAY_URLS.includes(url))
} }
export function recommendRelaysByLanguage(i18nLanguage: string) {
if (i18nLanguage.startsWith('zh')) {
return [
'wss://relay.nostrzh.org/',
'wss://relay.nostr.moe/',
'wss://lang.relays.land/zh',
'wss://relay.stream/'
]
}
if (i18nLanguage.startsWith('ja')) {
return ['wss://yabu.me/', 'wss://lang.relays.land/ja']
}
if (i18nLanguage.startsWith('es')) {
return ['wss://lang.relays.land/es']
}
if (i18nLanguage.startsWith('it')) {
return ['wss://lang.relays.land/it']
}
if (i18nLanguage.startsWith('pt')) {
return ['wss://lang.relays.land/pt']
}
return []
}

11
src/types/index.d.ts vendored
View File

@@ -1,5 +1,10 @@
import { Event, Filter, VerifiedEvent } from 'nostr-tools' import { Event, Filter, VerifiedEvent } from 'nostr-tools'
import { MEDIA_AUTO_LOAD_POLICY, NOTIFICATION_LIST_STYLE, NSFW_DISPLAY_POLICY, POLL_TYPE } from '../constants' import {
MEDIA_AUTO_LOAD_POLICY,
NOTIFICATION_LIST_STYLE,
NSFW_DISPLAY_POLICY,
POLL_TYPE
} from '../constants'
export type TSubRequestFilter = Omit<Filter, 'since' | 'until'> & { limit: number } export type TSubRequestFilter = Omit<Filter, 'since' | 'until'> & { limit: number }
@@ -200,12 +205,10 @@ export type TNotificationStyle =
export type TAwesomeRelayCollection = { export type TAwesomeRelayCollection = {
id: string id: string
name: string name: string
description: string
relays: string[] relays: string[]
} }
export type TMediaAutoLoadPolicy = export type TMediaAutoLoadPolicy =
(typeof MEDIA_AUTO_LOAD_POLICY)[keyof typeof MEDIA_AUTO_LOAD_POLICY] (typeof MEDIA_AUTO_LOAD_POLICY)[keyof typeof MEDIA_AUTO_LOAD_POLICY]
export type TNsfwDisplayPolicy = export type TNsfwDisplayPolicy = (typeof NSFW_DISPLAY_POLICY)[keyof typeof NSFW_DISPLAY_POLICY]
(typeof NSFW_DISPLAY_POLICY)[keyof typeof NSFW_DISPLAY_POLICY]