From 963ce098c64a31abd0ddbc93f9bc166a8bf2e60e Mon Sep 17 00:00:00 2001 From: Martti Malmi Date: Fri, 17 Nov 2023 10:07:16 +0200 Subject: [PATCH 01/11] system/SocialGraph, socialGraphInstance --- packages/app/src/Cache/FollowListCache.ts | 7 +- .../app/src/Element/User/ProfileImage.tsx | 7 +- packages/app/src/Login/MultiAccountStore.ts | 10 +- packages/app/src/Pages/NetworkGraph.tsx | 16 +- .../src/SocialGraph/SocialGraph.ts | 176 +++++++++--------- .../src/SocialGraph/UniqueIds.ts | 0 packages/system/src/index.ts | 2 + 7 files changed, 114 insertions(+), 104 deletions(-) rename packages/{app => system}/src/SocialGraph/SocialGraph.ts (65%) rename packages/{app => system}/src/SocialGraph/UniqueIds.ts (100%) diff --git a/packages/app/src/Cache/FollowListCache.ts b/packages/app/src/Cache/FollowListCache.ts index 34f60dcb..ca4cdbf6 100644 --- a/packages/app/src/Cache/FollowListCache.ts +++ b/packages/app/src/Cache/FollowListCache.ts @@ -1,9 +1,8 @@ import { db } from "Db"; import { unixNowMs } from "@snort/shared"; -import { EventKind, RequestBuilder, TaggedNostrEvent } from "@snort/system"; +import { EventKind, RequestBuilder, socialGraphInstance, TaggedNostrEvent } from "@snort/system"; import { RefreshFeedCache } from "./RefreshFeedCache"; import { LoginSession } from "Login"; -import SocialGraph from "SocialGraph/SocialGraph"; export class FollowListCache extends RefreshFeedCache { constructor() { @@ -27,7 +26,7 @@ export class FollowListCache extends RefreshFeedCache { loaded: unixNowMs(), }); if (update !== "no_change") { - SocialGraph.handleFollowEvent(e); + socialGraphInstance.handleFollowEvent(e); } }), ); @@ -43,6 +42,6 @@ export class FollowListCache extends RefreshFeedCache { override async preload() { await super.preload(); - this.snapshot().forEach(e => SocialGraph.handleFollowEvent(e)); + this.snapshot().forEach(e => socialGraphInstance.handleFollowEvent(e)); } } diff --git a/packages/app/src/Element/User/ProfileImage.tsx b/packages/app/src/Element/User/ProfileImage.tsx index 9be02460..13d689a7 100644 --- a/packages/app/src/Element/User/ProfileImage.tsx +++ b/packages/app/src/Element/User/ProfileImage.tsx @@ -1,7 +1,7 @@ import "./ProfileImage.css"; import React, { ReactNode } from "react"; -import { HexKey, UserMetadata } from "@snort/system"; +import { HexKey, socialGraphInstance, UserMetadata } from "@snort/system"; import { useUserProfile } from "@snort/system-react"; import { useHover } from "@uidotdev/usehooks"; import classNames from "classnames"; @@ -12,7 +12,6 @@ import Icon from "Icons/Icon"; import DisplayName from "./DisplayName"; import { ProfileLink } from "./ProfileLink"; import { ProfileCard } from "./ProfileCard"; -import SocialGraph from "../../SocialGraph/SocialGraph"; export interface ProfileImageProps { pubkey: HexKey; @@ -51,7 +50,7 @@ export default function ProfileImage({ }: ProfileImageProps) { const user = useUserProfile(profile ? "" : pubkey) ?? profile; const nip05 = defaultNip ? defaultNip : user?.nip05; - const followDistance = SocialGraph.getFollowDistance(pubkey); + const followDistance = socialGraphInstance.getFollowDistance(pubkey); const [ref, hovering] = useHover(); function handleClick(e: React.MouseEvent) { @@ -65,7 +64,7 @@ export default function ProfileImage({ let followDistanceColor = ""; if (followDistance <= 1) { followDistanceColor = "success"; - } else if (followDistance === 2 && SocialGraph.followedByFriendsCount(pubkey) >= 10) { + } else if (followDistance === 2 && socialGraphInstance.followedByFriendsCount(pubkey) >= 10) { followDistanceColor = "text-nostr-orange"; } return ( diff --git a/packages/app/src/Login/MultiAccountStore.ts b/packages/app/src/Login/MultiAccountStore.ts index a26f5e25..6764ffb9 100644 --- a/packages/app/src/Login/MultiAccountStore.ts +++ b/packages/app/src/Login/MultiAccountStore.ts @@ -2,7 +2,7 @@ import * as secp from "@noble/curves/secp256k1"; import * as utils from "@noble/curves/abstract/utils"; import { v4 as uuid } from "uuid"; -import { HexKey, RelaySettings, EventPublisher, KeyStorage, NotEncrypted } from "@snort/system"; +import { HexKey, RelaySettings, EventPublisher, KeyStorage, NotEncrypted, socialGraphInstance } from "@snort/system"; import { deepClone, sanitizeRelayUrl, unwrap, ExternalStore } from "@snort/shared"; import { DefaultRelays } from "Const"; @@ -74,6 +74,10 @@ export class MultiAccountStore extends ExternalStore { if (!this.#activeAccount) { this.#activeAccount = this.#accounts.keys().next().value; } + if (this.#activeAccount) { + const pubKey = this.#accounts.get(this.#activeAccount)?.publicKey; + socialGraphInstance.setRoot(pubKey || ""); + } for (const [, v] of this.#accounts) { // reset readonly on load if (v.type === LoginSessionType.PrivateKey && v.readonly) { @@ -116,6 +120,8 @@ export class MultiAccountStore extends ExternalStore { switchAccount(id: string) { if (this.#accounts.has(id)) { this.#activeAccount = id; + const pubKey = this.#accounts.get(id)?.publicKey || ""; + socialGraphInstance.setRoot(pubKey); this.#save(); } } @@ -140,6 +146,7 @@ export class MultiAccountStore extends ExternalStore { if (this.#accounts.has(key)) { throw new Error("Already logged in with this pubkey"); } + socialGraphInstance.setRoot(key); const initRelays = this.decideInitRelays(relays); const newSession = { ...LoggedOut, @@ -180,6 +187,7 @@ export class MultiAccountStore extends ExternalStore { if (this.#accounts.has(pubKey)) { throw new Error("Already logged in with this pubkey"); } + socialGraphInstance.setRoot(pubKey); const initRelays = this.decideInitRelays(relays); const newSession = { ...LoggedOut, diff --git a/packages/app/src/Pages/NetworkGraph.tsx b/packages/app/src/Pages/NetworkGraph.tsx index d5e40272..fdd5bd5a 100644 --- a/packages/app/src/Pages/NetworkGraph.tsx +++ b/packages/app/src/Pages/NetworkGraph.tsx @@ -1,8 +1,6 @@ import ForceGraph3D, { NodeObject } from "react-force-graph-3d"; import { useContext, useEffect, useState } from "react"; -import { STR, UID } from "../SocialGraph/UniqueIds"; -import SocialGraph from "../SocialGraph/SocialGraph"; -import { MetadataCache } from "@snort/system"; +import { MetadataCache, socialGraphInstance, STR, UID } from "@snort/system"; import { SnortContext } from "@snort/system-react"; import * as THREE from "three"; import { defaultAvatar } from "../SnortUtils"; @@ -96,17 +94,17 @@ const NetworkGraph = () => { const nodesVisited = new Set(); const userCountByDistance = Array.from( { length: 6 }, - (_, i) => SocialGraph.usersByFollowDistance.get(i)?.size || 0, + (_, i) => socialGraphInstance.usersByFollowDistance.get(i)?.size || 0, ); // Go through all the nodes for (let distance = 0; distance <= showDistance; ++distance) { - const users = SocialGraph.usersByFollowDistance.get(distance); + const users = socialGraphInstance.usersByFollowDistance.get(distance); if (!users) break; for (const UID of users) { if (renderLimit && nodes.size >= renderLimit) break; // Temporary hack - const inboundCount = SocialGraph.followersByUser.get(UID)?.size || 0; - const outboundCount = SocialGraph.followedByUser.get(UID)?.size || 0; + const inboundCount = socialGraphInstance.followersByUser.get(UID)?.size || 0; + const outboundCount = socialGraphInstance.followedByUser.get(UID)?.size || 0; const pubkey = STR(UID); const node = { id: UID, @@ -132,7 +130,7 @@ const NetworkGraph = () => { // Add links for (const node of nodes.values()) { if (direction === Direction.OUTBOUND || direction === Direction.BOTH) { - for (const followedID of SocialGraph.followedByUser.get(node.id) ?? []) { + for (const followedID of socialGraphInstance.followedByUser.get(node.id) ?? []) { if (!nodes.has(followedID)) continue; // Skip links to nodes that we're not rendering if (nodesVisited.has(followedID)) continue; links.push({ @@ -144,7 +142,7 @@ const NetworkGraph = () => { } // TODO: Fix filtering /* if (direction === Direction.INBOUND || direction === Direction.BOTH) { - for (const followerID of SocialGraph.followersByUser.get(node.id) ?? []) { + for (const followerID of socialGraphInstance.followersByUser.get(node.id) ?? []) { if (nodesVisited.has(followerID)) continue; const follower = nodes.get(followerID); if (!follower) continue; // Skip links to nodes that we're not rendering diff --git a/packages/app/src/SocialGraph/SocialGraph.ts b/packages/system/src/SocialGraph/SocialGraph.ts similarity index 65% rename from packages/app/src/SocialGraph/SocialGraph.ts rename to packages/system/src/SocialGraph/SocialGraph.ts index 13051901..c3264aea 100644 --- a/packages/app/src/SocialGraph/SocialGraph.ts +++ b/packages/system/src/SocialGraph/SocialGraph.ts @@ -1,27 +1,53 @@ import { ID, STR, UID } from "./UniqueIds"; -import { LoginStore } from "../Login"; -import { unwrap } from "../SnortUtils"; -import { HexKey, MetadataCache, NostrEvent } from "@snort/system"; +import { HexKey, NostrEvent } from ".."; -type Unsubscribe = () => void; +export default class SocialGraph { + root: UID; + followDistanceByUser = new Map(); + usersByFollowDistance = new Map>(); + followedByUser = new Map>(); + followersByUser = new Map>(); + latestFollowEventTimestamps = new Map(); -const Key = { - pubKey: null as HexKey | null, - getPubKey: () => { - return unwrap(LoginStore.snapshot().publicKey); - }, - isMine: (user: HexKey) => user === Key.getPubKey(), -}; + constructor(root: HexKey) { + this.root = ID(root); + this.followDistanceByUser.set(this.root, 0); + this.usersByFollowDistance.set(0, new Set([this.root])); + } -export default { - followDistanceByUser: new Map(), - usersByFollowDistance: new Map>(), - profiles: new Map(), // JSON.parsed event.content of profile events - followedByUser: new Map>(), - followersByUser: new Map>(), - latestFollowEventTimestamps: new Map(), + setRoot(root: HexKey) { + const rootId = ID(root); + if (rootId === this.root) { + return; + } + this.root = rootId; + this.followDistanceByUser.clear(); + this.usersByFollowDistance.clear(); + this.followDistanceByUser.set(this.root, 0); + this.usersByFollowDistance.set(0, new Set([this.root])); - handleFollowEvent: function (event: NostrEvent) { + const queue = [this.root]; + + while (queue.length > 0) { + const user = queue.shift()!; + const distance = this.followDistanceByUser.get(user)!; + + const followers = this.followersByUser.get(user) || new Set(); + for (const follower of followers) { + if (!this.followDistanceByUser.has(follower)) { + const newFollowDistance = distance + 1; + this.followDistanceByUser.set(follower, newFollowDistance); + if (!this.usersByFollowDistance.has(newFollowDistance)) { + this.usersByFollowDistance.set(newFollowDistance, new Set()); + } + this.usersByFollowDistance.get(newFollowDistance)!.add(follower); + queue.push(follower); + } + } + } + } + + handleFollowEvent(event: NostrEvent) { try { const author = ID(event.pubkey); const timestamp = event.created_at; @@ -59,27 +85,27 @@ export default { } catch (e) { // might not be logged in or sth } - }, + } - isFollowing: function (follower: HexKey, followedUser: HexKey): boolean { + isFollowing(follower: HexKey, followedUser: HexKey): boolean { const followedUserId = ID(followedUser); const followerId = ID(follower); return !!this.followedByUser.get(followerId)?.has(followedUserId); - }, + } - getFollowDistance: function (user: HexKey): number { + getFollowDistance(user: HexKey): number { try { - if (Key.isMine(user)) { + const userId = ID(user); + if (userId === this.root) { return 0; } - const userId = ID(user); const distance = this.followDistanceByUser.get(userId); return distance === undefined ? 1000 : distance; } catch (e) { // might not be logged in or sth return 1000; } - }, + } addUserByFollowDistance(distance: number, user: UID) { if (!this.usersByFollowDistance.has(distance)) { @@ -101,21 +127,12 @@ export default { this.usersByFollowDistance.get(d)?.delete(user); } } - }, + } - ensureRootUser: function () { - const myId = ID(Key.getPubKey()); - if (myId && !this.followDistanceByUser.has(myId)) { - this.followDistanceByUser.set(myId, 0); - this.addUserByFollowDistance(0, myId); - } - }, - - addFollower: function (followedUser: UID, follower: UID) { + addFollower(followedUser: UID, follower: UID) { if (typeof followedUser !== "number" || typeof follower !== "number") { throw new Error("Invalid user id"); } - this.ensureRootUser(); if (!this.followersByUser.has(followedUser)) { this.followersByUser.set(followedUser, new Set()); } @@ -124,11 +141,10 @@ export default { if (!this.followedByUser.has(follower)) { this.followedByUser.set(follower, new Set()); } - const myId = ID(Key.getPubKey()); - if (followedUser !== myId) { + if (followedUser !== this.root) { let newFollowDistance; - if (follower === myId) { + if (follower === this.root) { // basically same as the next "else" block, but faster newFollowDistance = 1; this.addUserByFollowDistance(newFollowDistance, followedUser); @@ -145,19 +161,20 @@ export default { } this.followedByUser.get(follower)?.add(followedUser); - if (this.followedByUser.get(myId)?.has(follower)) { + if (this.followedByUser.get(this.root)?.has(follower)) { /* setTimeout(() => { PubSub.subscribe({ authors: [STR(followedUser)], kinds: [0, 3] }, undefined, true); }, 0); */ } - }, - removeFollower: function (unfollowedUser: UID, follower: UID) { + } + + removeFollower(unfollowedUser: UID, follower: UID) { this.followersByUser.get(unfollowedUser)?.delete(follower); this.followedByUser.get(follower)?.delete(unfollowedUser); - if (unfollowedUser === ID(Key.getPubKey())) { + if (unfollowedUser === this.root) { return; } @@ -175,60 +192,47 @@ export default { } else { this.followDistanceByUser.set(unfollowedUser, smallest); } - }, + } + // TODO subscription methods for followersByUser and followedByUser. and maybe messagesByTime. and replies - followerCount: function (address: HexKey) { + followerCount(address: HexKey) { const id = ID(address); return this.followersByUser.get(id)?.size ?? 0; - }, - followedByFriendsCount: function (address: HexKey) { + } + + followedByFriendsCount(address: HexKey) { let count = 0; - const myId = ID(Key.getPubKey()); const id = ID(address); for (const follower of this.followersByUser.get(id) ?? []) { - if (this.followedByUser.get(myId)?.has(follower)) { + if (this.followedByUser.get(this.root)?.has(follower)) { count++; // should we stop at 10? } } return count; - }, - getFollowedByUser: function ( - user: HexKey, - cb?: (followedUsers: Set) => void, - includeSelf = false, - ): Unsubscribe { + } + + getFollowedByUser(user: HexKey, includeSelf = false): Set { const userId = ID(user); - const callback = () => { - if (cb) { - const set = new Set(); - for (const id of this.followedByUser.get(userId) || []) { - set.add(STR(id)); - } - if (includeSelf) { - set.add(user); - } - cb(set); - } - }; - if (this.followedByUser.has(userId) || includeSelf) { - callback(); + const set = new Set(); + for (const id of this.followedByUser.get(userId) || []) { + set.add(STR(id)); + } + if (includeSelf) { + set.add(user); } //return PubSub.subscribe({ kinds: [3], authors: [user] }, callback); - return () => {}; - }, - getFollowersByUser: function (address: HexKey, cb?: (followers: Set) => void): Unsubscribe { + return set; + } + + getFollowersByUser(address: HexKey): Set { const userId = ID(address); - const callback = () => { - if (cb) { - const set = new Set(); - for (const id of this.followersByUser.get(userId) || []) { - set.add(STR(id)); - } - cb(set); - } - }; - this.followersByUser.has(userId) && callback(); + const set = new Set(); + for (const id of this.followersByUser.get(userId) || []) { + set.add(STR(id)); + } //return PubSub.subscribe({ kinds: [3], '#p': [address] }, callback); // TODO this doesn't fire when a user is unfollowed - return () => {}; - }, -}; + return set; + } +} + +export const socialGraphInstance = new SocialGraph(""); diff --git a/packages/app/src/SocialGraph/UniqueIds.ts b/packages/system/src/SocialGraph/UniqueIds.ts similarity index 100% rename from packages/app/src/SocialGraph/UniqueIds.ts rename to packages/system/src/SocialGraph/UniqueIds.ts diff --git a/packages/system/src/index.ts b/packages/system/src/index.ts index c226a857..768bd5a2 100644 --- a/packages/system/src/index.ts +++ b/packages/system/src/index.ts @@ -10,6 +10,8 @@ import { base64 } from "@scure/base"; export * from "./nostr-system"; export { default as EventKind } from "./event-kind"; +export { default as SocialGraph, socialGraphInstance } from "./SocialGraph/SocialGraph"; +export * from "./SocialGraph/UniqueIds"; export * from "./nostr"; export * from "./links"; export * from "./nips"; From 8666467efcc3248b00d5ef23a7069720d108359f Mon Sep 17 00:00:00 2001 From: Martti Malmi Date: Fri, 17 Nov 2023 12:02:08 +0200 Subject: [PATCH 02/11] feed all follow events to socialGraphInstance.handleFollowEvent --- packages/app/src/index.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 2f49c9f9..1ff64552 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -19,6 +19,7 @@ import { mapEventToProfile, PowWorker, encodeTLVEntries, + socialGraphInstance, } from "@snort/system"; import { SnortContext } from "@snort/system-react"; import { removeUndefined, throwIfOffline } from "@snort/shared"; @@ -109,6 +110,12 @@ System.on("auth", async (c, r, cb) => { } }); +System.on("event", ev => { + if (ev.kind === 3) { + socialGraphInstance.handleFollowEvent(ev); + } +}); + async function fetchProfile(key: string) { try { throwIfOffline(); From 588c3756fd02a2971c857dd9a53ce5e1ff0410df Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 17 Nov 2023 20:03:59 +0000 Subject: [PATCH 03/11] feat: use close relays --- packages/app/src/Element/Relay/Relay.css | 1 - packages/app/src/Element/Relay/Relay.tsx | 2 +- packages/app/src/External/SnortApi.ts | 14 ++++ packages/app/src/Login/Functions.ts | 18 +++- packages/app/src/Pages/settings/Relays.tsx | 95 +++++++++++++++++++++- packages/app/src/SnortUtils/index.ts | 9 +- packages/app/src/lang.json | 12 +++ packages/app/src/translations/en.json | 4 + 8 files changed, 146 insertions(+), 9 deletions(-) diff --git a/packages/app/src/Element/Relay/Relay.css b/packages/app/src/Element/Relay/Relay.css index 23854ef1..eae0f514 100644 --- a/packages/app/src/Element/Relay/Relay.css +++ b/packages/app/src/Element/Relay/Relay.css @@ -1,5 +1,4 @@ .relay { - background-color: var(--gray-secondary); border-radius: 5px; display: grid; grid-template-columns: min-content auto; diff --git a/packages/app/src/Element/Relay/Relay.tsx b/packages/app/src/Element/Relay/Relay.tsx index 5549437c..ad6a1242 100644 --- a/packages/app/src/Element/Relay/Relay.tsx +++ b/packages/app/src/Element/Relay/Relay.tsx @@ -41,7 +41,7 @@ export default function Relay(props: RelayProps) { return ( <> -
+
diff --git a/packages/app/src/External/SnortApi.ts b/packages/app/src/External/SnortApi.ts index 39d517c2..15b78a81 100644 --- a/packages/app/src/External/SnortApi.ts +++ b/packages/app/src/External/SnortApi.ts @@ -68,6 +68,16 @@ export interface TranslationResponse { }>; } +export interface RelayDistance { + url: string; + distance: number; + users: number; + country?: string; + city?: string; + is_paid?: boolean; + description?: string; +} + export default class SnortApi { #url: string; #publisher?: EventPublisher; @@ -121,6 +131,10 @@ export default class SnortApi { return this.#getJson("api/v1/translate", "POST", tx); } + closeRelays(lat: number, lon: number, count = 5) { + return this.#getJson>(`api/v1/relays?count=${count}`, "POST", { lat, lon }); + } + async #getJsonAuthd( path: string, method?: "GET" | string, diff --git a/packages/app/src/Login/Functions.ts b/packages/app/src/Login/Functions.ts index 6b4fb918..25da0bef 100644 --- a/packages/app/src/Login/Functions.ts +++ b/packages/app/src/Login/Functions.ts @@ -15,10 +15,11 @@ import * as utils from "@noble/curves/abstract/utils"; import { DefaultRelays, SnortPubKey } from "Const"; import { LoginStore, UserPreferences, LoginSession, LoginSessionType, SnortAppData, Newest } from "Login"; import { generateBip39Entropy, entropyToPrivateKey } from "nip6"; -import { bech32ToHex, dedupeById, sanitizeRelayUrl, unwrap } from "SnortUtils"; +import { bech32ToHex, dedupeById, getCountry, sanitizeRelayUrl, unwrap } from "SnortUtils"; import { SubscriptionEvent } from "Subscription"; import { Chats, FollowsFeed, GiftsCache, Notifications } from "Cache"; import { Nip7OsSigner } from "./Nip7OsSigner"; +import SnortApi from "External/SnortApi"; export function setRelays(state: LoginSession, relays: Record, createdAt: number) { if (SINGLE_RELAY) { @@ -94,6 +95,21 @@ export async function generateNewLogin( const privateKey = entropyToPrivateKey(ent); const newRelays = Object.fromEntries(DefaultRelays.entries()); + // Use current timezone info to determine approx location + // use closest 5 relays + const country = getCountry(); + const api = new SnortApi(); + const closeRelays = await api.closeRelays(country.lat, country.lon, 10); + for (const cr of closeRelays.sort((a, b) => (a.distance > b.distance ? 1 : -1)).filter(a => !a.is_paid)) { + const rr = sanitizeRelayUrl(cr.url); + if (rr) { + newRelays[rr] = { read: true, write: true }; + if (Object.keys(newRelays).length >= 5) { + break; + } + } + } + // connect to new relays await Promise.all(Object.entries(newRelays).map(([k, v]) => system.ConnectToRelay(k, v))); diff --git a/packages/app/src/Pages/settings/Relays.tsx b/packages/app/src/Pages/settings/Relays.tsx index b15641df..a3b8eb8e 100644 --- a/packages/app/src/Pages/settings/Relays.tsx +++ b/packages/app/src/Pages/settings/Relays.tsx @@ -1,6 +1,6 @@ -import { useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { FormattedMessage } from "react-intl"; -import { unixNowMs } from "@snort/shared"; +import { unixNowMs, unwrap } from "@snort/shared"; import { EventPublisher, FullRelaySettings, RelaySettings, SystemInterface } from "@snort/system"; import Relay from "Element/Relay/Relay"; @@ -8,9 +8,11 @@ import useEventPublisher from "Hooks/useEventPublisher"; import useLogin from "Hooks/useLogin"; import { setRelays } from "Login"; import AsyncButton from "Element/AsyncButton"; +import SnortApi, { RelayDistance } from "External/SnortApi"; +import { getCountry, getRelayName, sanitizeRelayUrl } from "SnortUtils"; +import { formatShort } from "Number"; import messages from "./messages"; - const Blasters = ["wss://nostr.mutinywallet.com"]; export async function saveRelays( @@ -88,6 +90,7 @@ const RelaySettingsPage = () => { {addRelay()} +

@@ -101,3 +104,89 @@ const RelaySettingsPage = () => { }; export default RelaySettingsPage; + +export function CloseRelays() { + const [relays, setRecommendedRelays] = useState>(); + const country = getCountry(); + const [location, setLocation] = useState<{ lat: number; lon: number }>(country); + const login = useLogin(); + const relayUrls = Object.keys(login.relays.item); + + async function loadRelays() { + const api = new SnortApi(); + setRecommendedRelays(await api.closeRelays(location.lat, location.lon, 10)); + } + + useEffect(() => { + loadRelays().catch(console.error); + }, [location]); + + return ( + <> +

+ +

+ {"geolocation" in navigator && ( + { + try { + const pos = await new Promise((resolve, reject) => { + navigator.geolocation.getCurrentPosition(resolve, reject); + }); + setLocation({ + lat: pos.coords.latitude, + lon: pos.coords.longitude, + }); + } catch (e) { + console.error(e); + } + }}> + + + )} + {relays + ?.filter(a => !relayUrls.includes(unwrap(sanitizeRelayUrl(a.url))) && !a.is_paid) + .sort((a, b) => (a.distance > b.distance ? 1 : -1)) + .map(a => ( +
+
+
{getRelayName(a.url)}
+ { + setRelays( + login, + { + ...login.relays.item, + [a.url]: { read: true, write: true }, + }, + unixNowMs(), + ); + }}> + + +
+
+ {a.description} + + + + + + +
+
+ ))} + + ); +} diff --git a/packages/app/src/SnortUtils/index.ts b/packages/app/src/SnortUtils/index.ts index 08b47329..198e306f 100644 --- a/packages/app/src/SnortUtils/index.ts +++ b/packages/app/src/SnortUtils/index.ts @@ -517,12 +517,15 @@ export function getDisplayNameOrPlaceHolder(user: UserMetadata | undefined, pubk export function getCountry() { const tz = Intl.DateTimeFormat().resolvedOptions(); const info = (TZ as Record | undefined>)[tz.timeZone]; - const [, lat, lon] = info?.[1].split(/[-+]/) ?? ["", "00", "000"]; + const pos = info?.[1]; + const sep = Number(pos?.slice(1).search(/[-+]/)) + 1; + const [lat, lon] = [pos?.slice(0, sep) ?? "00", pos?.slice(sep) ?? "000"]; return { zone: tz.timeZone, country: info?.[0], - lat: Number(lat) / Math.pow(10, lat.length - 2), - lon: Number(lon) / Math.pow(10, lon.length - 3), + lat: Number(lat) / Math.pow(10, lat.length - 3), + lon: Number(lon) / Math.pow(10, lon.length - 4), + info, }; } diff --git a/packages/app/src/lang.json b/packages/app/src/lang.json index d2a3009b..2829da58 100644 --- a/packages/app/src/lang.json +++ b/packages/app/src/lang.json @@ -66,6 +66,9 @@ "0BUTMv": { "defaultMessage": "Search..." }, + "0HFX0T": { + "defaultMessage": "Use Exact Location" + }, "0jOEtS": { "defaultMessage": "Invalid LNURL" }, @@ -81,6 +84,9 @@ "0yO7wF": { "defaultMessage": "{n} secs" }, + "1H4Keq": { + "defaultMessage": "{n} users" + }, "1Mo59U": { "defaultMessage": "Are you sure you want to remove this note from bookmarks?" }, @@ -834,6 +840,9 @@ "UrKTqQ": { "defaultMessage": "You have an active iris.to account" }, + "VL900k": { + "defaultMessage": "Recommended Relays" + }, "VN0+Fz": { "defaultMessage": "Balance: {amount} sats" }, @@ -1158,6 +1167,9 @@ "jMzO1S": { "defaultMessage": "Internal error: {msg}" }, + "jTrbGf": { + "defaultMessage": "{n} km - {location}" + }, "jfV8Wr": { "defaultMessage": "Back", "description": "Navigate back button on threads view" diff --git a/packages/app/src/translations/en.json b/packages/app/src/translations/en.json index 5441acd8..471fc1d5 100644 --- a/packages/app/src/translations/en.json +++ b/packages/app/src/translations/en.json @@ -21,11 +21,13 @@ "08zn6O": "Export Keys", "0Azlrb": "Manage", "0BUTMv": "Search...", + "0HFX0T": "Use Exact Location", "0jOEtS": "Invalid LNURL", "0mch2Y": "name has disallowed characters", "0siT4z": "Politics", "0uoY11": "Show Status", "0yO7wF": "{n} secs", + "1H4Keq": "{n} users", "1Mo59U": "Are you sure you want to remove this note from bookmarks?", "1R43+L": "Enter Nostr Wallet Connect config", "1c4YST": "Connected to: {node} 🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "Sign In", "Up5U7K": "Block", "UrKTqQ": "You have an active iris.to account", + "VL900k": "Recommended Relays", "VN0+Fz": "Balance: {amount} sats", "VOjC1i": "Pick which upload service you want to upload attachments to", "VR5eHw": "Public key (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "Your {site_name} subscription is expired", "jHa/ko": "Clean up your feed", "jMzO1S": "Internal error: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "Back", "jvo0vs": "Save", "jzgQ2z": "{n} Reactions", From 3c2bfbb4c6fe762bb329865f26a752ead57f56ff Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 17 Nov 2023 20:21:40 +0000 Subject: [PATCH 04/11] chore: tweak relays --- packages/app/config/default.json | 2 +- packages/app/config/iris.json | 2 +- packages/app/src/Login/Functions.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/app/config/default.json b/packages/app/config/default.json index cd02a553..f0c779ee 100644 --- a/packages/app/config/default.json +++ b/packages/app/config/default.json @@ -20,6 +20,6 @@ "defaultRelays": { "wss://relay.snort.social/": { "read": true, "write": true }, "wss://nostr.wine/": { "read": true, "write": false }, - "wss://nos.lol/": { "read": true, "write": true } + "wss://eden.nostr.land/": { "read": true, "write": false } } } diff --git a/packages/app/config/iris.json b/packages/app/config/iris.json index 9ddd1c9b..49d54b10 100644 --- a/packages/app/config/iris.json +++ b/packages/app/config/iris.json @@ -19,6 +19,6 @@ "defaultRelays": { "wss://relay.snort.social/": { "read": true, "write": true }, "wss://nostr.wine/": { "read": true, "write": false }, - "wss://nos.lol/": { "read": true, "write": true } + "wss://eden.nostr.land/": { "read": true, "write": false } } } diff --git a/packages/app/src/Login/Functions.ts b/packages/app/src/Login/Functions.ts index 25da0bef..21a2bd42 100644 --- a/packages/app/src/Login/Functions.ts +++ b/packages/app/src/Login/Functions.ts @@ -12,7 +12,7 @@ import { unixNowMs } from "@snort/shared"; import * as secp from "@noble/curves/secp256k1"; import * as utils from "@noble/curves/abstract/utils"; -import { DefaultRelays, SnortPubKey } from "Const"; +import { SnortPubKey } from "Const"; import { LoginStore, UserPreferences, LoginSession, LoginSessionType, SnortAppData, Newest } from "Login"; import { generateBip39Entropy, entropyToPrivateKey } from "nip6"; import { bech32ToHex, dedupeById, getCountry, sanitizeRelayUrl, unwrap } from "SnortUtils"; @@ -93,13 +93,13 @@ export async function generateNewLogin( const ent = generateBip39Entropy(); const entropy = utils.bytesToHex(ent); const privateKey = entropyToPrivateKey(ent); - const newRelays = Object.fromEntries(DefaultRelays.entries()); + const newRelays = {} as Record; // Use current timezone info to determine approx location // use closest 5 relays const country = getCountry(); const api = new SnortApi(); - const closeRelays = await api.closeRelays(country.lat, country.lon, 10); + const closeRelays = await api.closeRelays(country.lat, country.lon, 20); for (const cr of closeRelays.sort((a, b) => (a.distance > b.distance ? 1 : -1)).filter(a => !a.is_paid)) { const rr = sanitizeRelayUrl(cr.url); if (rr) { From 839f4482316b575fb9ff9933a35bdfabd1c4ffae Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 17 Nov 2023 20:24:27 +0000 Subject: [PATCH 05/11] chore: Update translations --- packages/app/src/translations/ar_SA.json | 4 + packages/app/src/translations/az_AZ.json | 4 + packages/app/src/translations/de_DE.json | 4 + packages/app/src/translations/es_ES.json | 4 + packages/app/src/translations/fa_IR.json | 4 + packages/app/src/translations/fi_FI.json | 4 + packages/app/src/translations/fr_FR.json | 4 + packages/app/src/translations/hr_HR.json | 4 + packages/app/src/translations/hu_HU.json | 4 + packages/app/src/translations/id_ID.json | 4 + packages/app/src/translations/it_IT.json | 4 + packages/app/src/translations/ja_JP.json | 4 + packages/app/src/translations/nl_NL.json | 4 + packages/app/src/translations/pt_BR.json | 4 + packages/app/src/translations/ru_RU.json | 4 + packages/app/src/translations/sv_SE.json | 4 + packages/app/src/translations/sw_KE.json | 4 + packages/app/src/translations/ta_IN.json | 100 ++++++++++++----------- packages/app/src/translations/th_TH.json | 4 + packages/app/src/translations/zh_CN.json | 4 + packages/app/src/translations/zh_TW.json | 4 + 21 files changed, 132 insertions(+), 48 deletions(-) diff --git a/packages/app/src/translations/ar_SA.json b/packages/app/src/translations/ar_SA.json index b1e138d1..0b2dab05 100644 --- a/packages/app/src/translations/ar_SA.json +++ b/packages/app/src/translations/ar_SA.json @@ -21,11 +21,13 @@ "08zn6O": "مفاتيح التصدير", "0Azlrb": "إدارة", "0BUTMv": "بحث...", + "0HFX0T": "Use Exact Location", "0jOEtS": "عنوان LNURL غير صالح", "0mch2Y": "الاسم يحتوي على أحرف غير مسموح بها", "0siT4z": "السياسة", "0uoY11": "إظهار الحالة", "0yO7wF": "{n} ثانية", + "1H4Keq": "{n} users", "1Mo59U": "هل أنت متأكد من حذف هذا المنشور من المنشورات المرجعية؟", "1R43+L": "أدخل تكوين اتصال محفظة Nostr", "1c4YST": "متصل بـ: {node}🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "تسجيل الدخول", "Up5U7K": "حظر", "UrKTqQ": "لديك حساب iris.to نشط", + "VL900k": "Recommended Relays", "VN0+Fz": "الرصيد: {amount} ساتوشي", "VOjC1i": "اختر خدمة التحميل التي تريد رفع المرفقات إليها", "VR5eHw": "مفتاح عام (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "إنتهت صلاحية اشتراكك {site_name}", "jHa/ko": "تنظيف موجز الويب الخاص بك", "jMzO1S": "خطأ داخلي: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "الخلف", "jvo0vs": "حفظ", "jzgQ2z": "التفاعل {n}", diff --git a/packages/app/src/translations/az_AZ.json b/packages/app/src/translations/az_AZ.json index 5d2ede3c..6a26690f 100644 --- a/packages/app/src/translations/az_AZ.json +++ b/packages/app/src/translations/az_AZ.json @@ -21,11 +21,13 @@ "08zn6O": "Açarları ixrac edin", "0Azlrb": "İdarə et", "0BUTMv": "Axtar...", + "0HFX0T": "Use Exact Location", "0jOEtS": "Yanlış LNURL", "0mch2Y": "adda icazə verilməyən simvollar var", "0siT4z": "Politics", "0uoY11": "Show Status", "0yO7wF": "{n} saniyə", + "1H4Keq": "{n} users", "1Mo59U": "Bu qeydi əlfəcinlərdən silmək istədiyinizə əminsiniz?", "1R43+L": "Nostr Wallet Connect konfiqurasiyasını daxil edin", "1c4YST": "Qoşuldu: {node} 🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "Sign In", "Up5U7K": "Block", "UrKTqQ": "You have an active iris.to account", + "VL900k": "Recommended Relays", "VN0+Fz": "Balance: {amount} sats", "VOjC1i": "Pick which upload service you want to upload attachments to", "VR5eHw": "Public key (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "Your {site_name} subscription is expired", "jHa/ko": "Clean up your feed", "jMzO1S": "Internal error: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "Back", "jvo0vs": "Save", "jzgQ2z": "{n} Reactions", diff --git a/packages/app/src/translations/de_DE.json b/packages/app/src/translations/de_DE.json index 91296093..bd40d121 100644 --- a/packages/app/src/translations/de_DE.json +++ b/packages/app/src/translations/de_DE.json @@ -21,11 +21,13 @@ "08zn6O": "Schlüssel exportieren", "0Azlrb": "Verwalten", "0BUTMv": "Suche...", + "0HFX0T": "Use Exact Location", "0jOEtS": "Ungültige LNURL", "0mch2Y": "Der Name enthält unerlaubte Zeichen", "0siT4z": "Politik", "0uoY11": "Status anzeigen", "0yO7wF": "{n} Sek.", + "1H4Keq": "{n} users", "1Mo59U": "Bist du sicher, dass du diese Note aus deinen Lesezeichen entfernen möchtest?", "1R43+L": "Nostr Wallet Connect Konfiguration eingeben", "1c4YST": "Verbunden mit: {node}🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "Anmelden", "Up5U7K": "Blockieren", "UrKTqQ": "Du hast ein aktives iris.to Konto", + "VL900k": "Recommended Relays", "VN0+Fz": "Guthaben: {amount} Sats", "VOjC1i": "Wähle einen Upload-Dienst für deine Anhänge", "VR5eHw": "Öffentlicher Schlüssel (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "Dein {site_name} Abonnement ist abgelaufen", "jHa/ko": "Räume deinen Feed auf", "jMzO1S": "Interner Fehler: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "Zurück", "jvo0vs": "Speichern", "jzgQ2z": "{n} Reaktionen", diff --git a/packages/app/src/translations/es_ES.json b/packages/app/src/translations/es_ES.json index 08b2411a..71d1124e 100644 --- a/packages/app/src/translations/es_ES.json +++ b/packages/app/src/translations/es_ES.json @@ -21,11 +21,13 @@ "08zn6O": "Exportar claves", "0Azlrb": "Gestionar", "0BUTMv": "Buscar...", + "0HFX0T": "Use Exact Location", "0jOEtS": "LNURL inválida", "0mch2Y": "el nombre tiene caracteres inválidos", "0siT4z": "Política", "0uoY11": "Mostrar estado", "0yO7wF": "{n} seg", + "1H4Keq": "{n} users", "1Mo59U": "¿Estás seguro de que quieres eliminar esta nota de tus favoritos?", "1R43+L": "Introduzca la configuración de Nostr Wallet Connect", "1c4YST": "Conectado a: {node}🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "Iniciar sesión", "Up5U7K": "Bloquear", "UrKTqQ": "Tienes una cuenta activa en iris.to", + "VL900k": "Recommended Relays", "VN0+Fz": "Saldo: {amount} sats", "VOjC1i": "Elige qué servicio de subida de ficheros quieres utilizar", "VR5eHw": "Clave pública (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "Su suscripción a {site_name} ha caducado", "jHa/ko": "Limpie su alimentación", "jMzO1S": "Error interno: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "Atrás", "jvo0vs": "Guardar", "jzgQ2z": "{n} Reacciones", diff --git a/packages/app/src/translations/fa_IR.json b/packages/app/src/translations/fa_IR.json index 7e91da91..d6a9ca5f 100644 --- a/packages/app/src/translations/fa_IR.json +++ b/packages/app/src/translations/fa_IR.json @@ -21,11 +21,13 @@ "08zn6O": "استخراج کلید", "0Azlrb": "مدیریت", "0BUTMv": "جستجو...", + "0HFX0T": "Use Exact Location", "0jOEtS": "LNURL نامعتبر", "0mch2Y": "نام دارای کاراکتر غیرمجاز است", "0siT4z": "سیاست", "0uoY11": "نمایش وضعیت", "0yO7wF": "{n} ثانیه", + "1H4Keq": "{n} users", "1Mo59U": "مطمئنید می خواهید این یادداشت را از نشانک ها خارج کنید؟", "1R43+L": "پیکربندی اتصال ناستر به کیف پول", "1c4YST": "متصل به:{node}🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "ورود", "Up5U7K": "مسدود کردن", "UrKTqQ": "شما یک حساب کاربری فعال iris.to دارید", + "VL900k": "Recommended Relays", "VN0+Fz": "تراز: {amount} ساتوشی", "VOjC1i": "انتخاب کنید درکدام خدمات بارگذاری فایل می خواهید پیوست ها را بارگذاری کنید", "VR5eHw": "کلید عمومی (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "عضویت شما در {site_name} منقضی شده است", "jHa/ko": "پاک سازی خبرنامه", "jMzO1S": "خطای داخلی: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "برگشت", "jvo0vs": "ذخیره", "jzgQ2z": "{n} واکنش", diff --git a/packages/app/src/translations/fi_FI.json b/packages/app/src/translations/fi_FI.json index 387c7fa4..f66cc57f 100644 --- a/packages/app/src/translations/fi_FI.json +++ b/packages/app/src/translations/fi_FI.json @@ -21,11 +21,13 @@ "08zn6O": "Vie avaimet", "0Azlrb": "Hallitse", "0BUTMv": "Etsi...", + "0HFX0T": "Use Exact Location", "0jOEtS": "Virheellinen LNURL", "0mch2Y": "nimi sisältää sallimattomia merkkejä", "0siT4z": "Politiikka", "0uoY11": "Näytä tila", "0yO7wF": "{n} sek", + "1H4Keq": "{n} users", "1Mo59U": "Haluatko varmasti poistaa tämän viestin kirjanmerkeistä?", "1R43+L": "Anna Nostr Wallet Connect asetukset", "1c4YST": "Yhdistetty: {node} 🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "Kirjaudu sisään", "Up5U7K": "Estä", "UrKTqQ": "Sinulla on aktiivinen iris.to-tili", + "VL900k": "Recommended Relays", "VN0+Fz": "Saldo: {amount} satsia", "VOjC1i": "Valitse palvelu, johon haluat ladata liitteitä", "VR5eHw": "Julkinen avain (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "{site_name} tilauksesi on päättynyt", "jHa/ko": "Siivoa rehusi", "jMzO1S": "Sisäinen virhe: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "Takaisin", "jvo0vs": "Tallenna", "jzgQ2z": "{n} Reaktiota", diff --git a/packages/app/src/translations/fr_FR.json b/packages/app/src/translations/fr_FR.json index 97c16b0d..c4a73f57 100644 --- a/packages/app/src/translations/fr_FR.json +++ b/packages/app/src/translations/fr_FR.json @@ -21,11 +21,13 @@ "08zn6O": "Exporter les clés", "0Azlrb": "Gérer", "0BUTMv": "Chercher...", + "0HFX0T": "Use Exact Location", "0jOEtS": "LNURL invalide", "0mch2Y": "le nom contient des caractères non autorisés", "0siT4z": "Politique", "0uoY11": "Afficher l'état", "0yO7wF": "{n} secondes", + "1H4Keq": "{n} users", "1Mo59U": "Êtes-vous sûr de vouloir supprimer cette note de vos favoris ?", "1R43+L": "Accéder à la configuration de Nostr Wallet Connect", "1c4YST": "Connecté à : {node} 🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "S'inscrire", "Up5U7K": "Bloquer", "UrKTqQ": "Vous avez un compte iris.to actif", + "VL900k": "Recommended Relays", "VN0+Fz": "Solde : {amount} sats", "VOjC1i": "Choisissez le service d'hébergement vers lequel vous souhaitez héberger les pièces jointes", "VR5eHw": "Clé publique (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "Votre abonnement à {site_name} a expiré", "jHa/ko": "Nettoyez votre flux", "jMzO1S": "Erreur interne : {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "Retourner", "jvo0vs": "Sauvegarder", "jzgQ2z": "{n} Réactions", diff --git a/packages/app/src/translations/hr_HR.json b/packages/app/src/translations/hr_HR.json index 4f1fc22a..c1b40aaf 100644 --- a/packages/app/src/translations/hr_HR.json +++ b/packages/app/src/translations/hr_HR.json @@ -21,11 +21,13 @@ "08zn6O": "Izvezi Ključeve", "0Azlrb": "Upravljaj", "0BUTMv": "Pretraga...", + "0HFX0T": "Use Exact Location", "0jOEtS": "Nevažeći LNURL", "0mch2Y": "ime sadrži nepodržane znakove", "0siT4z": "Politics", "0uoY11": "Show Status", "0yO7wF": "{n} sekundi", + "1H4Keq": "{n} users", "1Mo59U": "Jeste li sigurni da želite ukloniti ovu bilješku iz trake s oznakama?", "1R43+L": "Enter Nostr Wallet Connect config", "1c4YST": "Povezano s: {node} 🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "Sign In", "Up5U7K": "Blokiraj", "UrKTqQ": "You have an active iris.to account", + "VL900k": "Recommended Relays", "VN0+Fz": "Iznos: {amount} sati", "VOjC1i": "Odaberite kroz koji servis želite učitati privitke", "VR5eHw": "Javni ključ (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "Your {site_name} subscription is expired", "jHa/ko": "Clean up your feed", "jMzO1S": "Interna pogreška: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "Natrag", "jvo0vs": "Spremi", "jzgQ2z": "{n} Reakcija", diff --git a/packages/app/src/translations/hu_HU.json b/packages/app/src/translations/hu_HU.json index 6e479a31..134c900d 100644 --- a/packages/app/src/translations/hu_HU.json +++ b/packages/app/src/translations/hu_HU.json @@ -21,11 +21,13 @@ "08zn6O": "Kulcsok exportálása", "0Azlrb": "Menedzselés", "0BUTMv": "Keresés...", + "0HFX0T": "Use Exact Location", "0jOEtS": "Érvénytelen LNURL", "0mch2Y": "név nem engedélyezett karaktereket tartalmaz", "0siT4z": "Politika", "0uoY11": "Státusz megjelenítése", "0yO7wF": "{n} másodperc", + "1H4Keq": "{n} users", "1Mo59U": "Biztos hogy a kedvencekből ezt a bejegyzést el akarod távolítani?", "1R43+L": "Írd be a Nostr Wallet Connect konfigurációt", "1c4YST": "Kapcsolódás a: {node} 🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "Bejelentkezés", "Up5U7K": "Tiltás", "UrKTqQ": "Van aktív iris.to fiókod", + "VL900k": "Recommended Relays", "VN0+Fz": "Egyenleg: {amount} sats", "VOjC1i": "Válaszd ki mely szolgáltatóhoz legyenek a fájlok feltöltve", "VR5eHw": "Publikus kulcs (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "{site_name}-előfizetése lejárt", "jHa/ko": "Tisztítsa meg a takarmányt", "jMzO1S": "Belső hiba: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "Vissza", "jvo0vs": "Mentés", "jzgQ2z": "{n} Reakció", diff --git a/packages/app/src/translations/id_ID.json b/packages/app/src/translations/id_ID.json index 585e2d50..ac28a3d5 100644 --- a/packages/app/src/translations/id_ID.json +++ b/packages/app/src/translations/id_ID.json @@ -21,11 +21,13 @@ "08zn6O": "Kunci Ekspor", "0Azlrb": "Mengelola", "0BUTMv": "Cari...", + "0HFX0T": "Use Exact Location", "0jOEtS": "LNURL Tidak valid", "0mch2Y": "nama memiliki karakter yang tidak diizinkan", "0siT4z": "Politik", "0uoY11": "Tampilkan Status", "0yO7wF": "{n} detik", + "1H4Keq": "{n} users", "1Mo59U": "Apa Anda yakin Anda ingin memindahkan catatan ini dari penanda buku?", "1R43+L": "Masukkan konfigurasi Nostr Wallet Connect", "1c4YST": "Terhubung ke: {node} 🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "Masuk", "Up5U7K": "Blokir", "UrKTqQ": "Anda memiliki akun iris.to yang aktif", + "VL900k": "Recommended Relays", "VN0+Fz": "Saldo: {amount} sats", "VOjC1i": "Pilih layanan unggah mana yang ingin Anda unggahkan lampirannya", "VR5eHw": "Kunci publik (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "Langganan {site_name} Anda telah kedaluwarsa", "jHa/ko": "Bersihkan feed Anda", "jMzO1S": "Kesalahan internal: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "Kembali", "jvo0vs": "Simpan", "jzgQ2z": "{n} Reaksi", diff --git a/packages/app/src/translations/it_IT.json b/packages/app/src/translations/it_IT.json index aa56312f..ff1d54f0 100644 --- a/packages/app/src/translations/it_IT.json +++ b/packages/app/src/translations/it_IT.json @@ -21,11 +21,13 @@ "08zn6O": "Esporta Chiavi", "0Azlrb": "Gestisci", "0BUTMv": "Cerca...", + "0HFX0T": "Use Exact Location", "0jOEtS": "LNURL non valido", "0mch2Y": "il nome ha caratteri non ammessi", "0siT4z": "Politica", "0uoY11": "Mostra stato", "0yO7wF": "{n} sec", + "1H4Keq": "{n} users", "1Mo59U": "Vuoi davvero rimuovere questa nota dai preferiti?", "1R43+L": "Inserire la configurazione di Nostr Wallet Connect", "1c4YST": "Connessione a: {node}🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "Accedi", "Up5U7K": "Blocca", "UrKTqQ": "Avete un account iris.to attivo", + "VL900k": "Recommended Relays", "VN0+Fz": "Saldo: {amount} sats", "VOjC1i": "Scegli quale servizio vuoi usare per caricare gli allegati", "VR5eHw": "Chiave pubblica (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "L'abbonamento a {site_name} è scaduto", "jHa/ko": "Pulite il vostro feed", "jMzO1S": "Errore interno: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "Indietro", "jvo0vs": "Salva", "jzgQ2z": "{n} Reazioni", diff --git a/packages/app/src/translations/ja_JP.json b/packages/app/src/translations/ja_JP.json index 3026a38e..93b64350 100644 --- a/packages/app/src/translations/ja_JP.json +++ b/packages/app/src/translations/ja_JP.json @@ -21,11 +21,13 @@ "08zn6O": "鍵をエクスポート", "0Azlrb": "管理", "0BUTMv": "検索する", + "0HFX0T": "Use Exact Location", "0jOEtS": "無効なLNURL", "0mch2Y": "名前に使用できない文字が含まれています", "0siT4z": "政治", "0uoY11": "ステータス表示", "0yO7wF": "{n}秒", + "1H4Keq": "{n} users", "1Mo59U": "本当にこの投稿をブックマークから削除しますか?", "1R43+L": "Nostr Wallet Connectの設定値を入力", "1c4YST": "{node}に接続しました🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "サインイン", "Up5U7K": "ブロック", "UrKTqQ": "iris.toアカウントをお持ちの方", + "VL900k": "Recommended Relays", "VN0+Fz": "残高: {amount} sats", "VOjC1i": "添付ファイルをアップロードするためのサービスを選択してください", "VR5eHw": "公開鍵 (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "{site_name} 、有効期限が切れています。", "jHa/ko": "フィードをきれいにする", "jMzO1S": "内部エラー:{msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "戻る", "jvo0vs": "保存", "jzgQ2z": "{n} リアクション", diff --git a/packages/app/src/translations/nl_NL.json b/packages/app/src/translations/nl_NL.json index abe00d48..abf8ed6b 100644 --- a/packages/app/src/translations/nl_NL.json +++ b/packages/app/src/translations/nl_NL.json @@ -21,11 +21,13 @@ "08zn6O": "Sleutel exporteren", "0Azlrb": "Beheren", "0BUTMv": "Zoeken naar...", + "0HFX0T": "Use Exact Location", "0jOEtS": "Ongeldig LNURL", "0mch2Y": "naam heeft verboden tekens", "0siT4z": "Politiek", "0uoY11": "Toon status", "0yO7wF": "{n} seconden", + "1H4Keq": "{n} users", "1Mo59U": "Weet u zeker dat u deze notitie uit bladwijzers wilt verwijderen?", "1R43+L": "Voer Nostr Wallet Connect configuratie in", "1c4YST": "Verbonden met: {node}🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "Aanmelden", "Up5U7K": "Blokkeren", "UrKTqQ": "Je hebt een actieve iris.to account", + "VL900k": "Recommended Relays", "VN0+Fz": "Saldo: {amount} sats", "VOjC1i": "Kies naar welke service u bijlagen wilt uploaden", "VR5eHw": "Publieke sleutel (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "Uw {site_name} abonnement is verlopen", "jHa/ko": "Ruim je feed op", "jMzO1S": "Interne fout: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "Terug", "jvo0vs": "Opslaan", "jzgQ2z": "{n} Reacties", diff --git a/packages/app/src/translations/pt_BR.json b/packages/app/src/translations/pt_BR.json index 194d3a2a..d8d58491 100644 --- a/packages/app/src/translations/pt_BR.json +++ b/packages/app/src/translations/pt_BR.json @@ -21,11 +21,13 @@ "08zn6O": "Exportar chaves", "0Azlrb": "Gerenciar", "0BUTMv": "Pesquisar...", + "0HFX0T": "Use Exact Location", "0jOEtS": "LNURL inválida", "0mch2Y": "o nome possui caracteres não permitidos", "0siT4z": "Política", "0uoY11": "Mostrar status", "0yO7wF": "{n} segundos", + "1H4Keq": "{n} users", "1Mo59U": "Tem certeza que deseja remover esta nota dos favoritos?", "1R43+L": "Insira a configuração da Nostr Wallet Connect", "1c4YST": "Conectado em: {node} 🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "Entrar", "Up5U7K": "Bloquear", "UrKTqQ": "Você tem uma conta ativa no iris.to", + "VL900k": "Recommended Relays", "VN0+Fz": "Saldo: {amount} sats", "VOjC1i": "Escolha para qual serviço fazer upload dos arquivos", "VR5eHw": "Chave pública (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "Sua assinatura do {site_name} expirou", "jHa/ko": "Limpe seu feed", "jMzO1S": "Erro interno: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "Voltar", "jvo0vs": "Salvar", "jzgQ2z": "{n} Reações", diff --git a/packages/app/src/translations/ru_RU.json b/packages/app/src/translations/ru_RU.json index f57b1a9e..6f09eb7c 100644 --- a/packages/app/src/translations/ru_RU.json +++ b/packages/app/src/translations/ru_RU.json @@ -21,11 +21,13 @@ "08zn6O": "Экспортировать ключи", "0Azlrb": "Управление", "0BUTMv": "Поиск...", + "0HFX0T": "Use Exact Location", "0jOEtS": "Неверный LNURL", "0mch2Y": "имя содержит запрещенные символы", "0siT4z": "Политика", "0uoY11": "Показать статус", "0yO7wF": "{n} секунд", + "1H4Keq": "{n} users", "1Mo59U": "Вы уверены, что хотите удалить эту заметку из закладок?", "1R43+L": "Введите конфигурацию Nostr Wallet Connect", "1c4YST": "Подключен к: {node} 🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "Войти", "Up5U7K": "Блок", "UrKTqQ": "У вас есть активная учетная запись iris.to", + "VL900k": "Recommended Relays", "VN0+Fz": "Баланс: {amount} сат", "VOjC1i": "Выберите каким сервисом Вы бы хотели пользоваться для загрузки медиа", "VR5eHw": "Публичный ключ (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "Срок действия вашей подписки {site_name} истек", "jHa/ko": "Очистка корма", "jMzO1S": "Ошибка: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "Назад", "jvo0vs": "Сохранить", "jzgQ2z": "{n} Реакции", diff --git a/packages/app/src/translations/sv_SE.json b/packages/app/src/translations/sv_SE.json index 2b76cfa4..fb17c071 100644 --- a/packages/app/src/translations/sv_SE.json +++ b/packages/app/src/translations/sv_SE.json @@ -21,11 +21,13 @@ "08zn6O": "Exportera nycklar", "0Azlrb": "Hantera", "0BUTMv": "Sök...", + "0HFX0T": "Use Exact Location", "0jOEtS": "Ogiltig LNURL", "0mch2Y": "namnet har otillåtna tecken", "0siT4z": "Politik", "0uoY11": "Visa status", "0yO7wF": "{n} secs", + "1H4Keq": "{n} users", "1Mo59U": "Är du säker på att du vill ta bort den här anteckningen från bokmärken?", "1R43+L": "Skriv in Nostr Wallet Connect konfiguration", "1c4YST": "Ansluten till: {node}🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "Logga in", "Up5U7K": "Blockera", "UrKTqQ": "Du har ett aktivt iris.to konto", + "VL900k": "Recommended Relays", "VN0+Fz": "Saldo: {amount} sats", "VOjC1i": "Välj vilken uppladdningstjänst du vill ladda upp bilagor till", "VR5eHw": "Publik nyckel (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "Ditt {site_name} abonnemang har löpt ut", "jHa/ko": "Städa upp i ditt flöde", "jMzO1S": "Internt fel: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "Tillbaka", "jvo0vs": "Spara", "jzgQ2z": "{n} Reaktioner", diff --git a/packages/app/src/translations/sw_KE.json b/packages/app/src/translations/sw_KE.json index fd1a72bc..e1072d1d 100644 --- a/packages/app/src/translations/sw_KE.json +++ b/packages/app/src/translations/sw_KE.json @@ -21,11 +21,13 @@ "08zn6O": "Tuma Funguo", "0Azlrb": "Dhibiti", "0BUTMv": "Tafuta...", + "0HFX0T": "Use Exact Location", "0jOEtS": "LNURL Batili", "0mch2Y": "jina limekataza herufi", "0siT4z": "Politics", "0uoY11": "Show Status", "0yO7wF": "{n} sekundi", + "1H4Keq": "{n} users", "1Mo59U": "Je, una uhakika unataka kuondoa dokezo hili kutoka kwa vialamisho?", "1R43+L": "Ingiza usanidi wa Nostr Wallet Connect", "1c4YST": "Imeunganishwa kwa: {node} 🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "Sign In", "Up5U7K": "Zuia", "UrKTqQ": "You have an active iris.to account", + "VL900k": "Recommended Relays", "VN0+Fz": "Salio: {amount} sats", "VOjC1i": "Chagua ni huduma gani ya upakiaji ungependa kupakia viambatisho", "VR5eHw": "Ufunguo wa umma (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "Your {site_name} subscription is expired", "jHa/ko": "Clean up your feed", "jMzO1S": "Hitilafu ya ndani: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "Rudi", "jvo0vs": "Hifadhi", "jzgQ2z": "{n} Maoni", diff --git a/packages/app/src/translations/ta_IN.json b/packages/app/src/translations/ta_IN.json index 8100360b..019d11d4 100644 --- a/packages/app/src/translations/ta_IN.json +++ b/packages/app/src/translations/ta_IN.json @@ -7,9 +7,9 @@ "+vA//S": "உள்நுழைவுகள்", "+vIQlC": "பிற்காலத்தில் உங்களது கணக்கை நிர்வகிக்க, கீழே உள்ள கடவுச்சொல்லைத் தவறாமல் சேமிக்கவும்", "+vVZ/G": "இணை", - "+vj0U3": "edit", + "+vj0U3": "தொகு", "+xliwN": "{name} மறுபதிவு செய்தார்", - "/B8zwF": "Your space the way you want it 😌", + "/B8zwF": "நீங்கள் விரும்பியபடி உங்கள் இடம் 😌", "/GCoTA": "அழி", "/JE/X+": "கணக்கு உதவி", "/PCavi": "பொது", @@ -21,29 +21,31 @@ "08zn6O": "சாவிகளை ஏற்றுமதி செய்யவும்", "0Azlrb": "நிர்வகி", "0BUTMv": "தேடு...", + "0HFX0T": "Use Exact Location", "0jOEtS": "தவறான LNURL", "0mch2Y": "பெயர் அங்கீகரிக்கப்படாத எழுத்துக்களைக் கொண்டுள்ளது", - "0siT4z": "Politics", + "0siT4z": "அரசியல்", "0uoY11": "சமுதாய நிலையைக் காட்டு", "0yO7wF": "{n} வினாடிகள்", + "1H4Keq": "{n} users", "1Mo59U": "இந்தக் குறிப்பைப் புக்மார்க்குகளிலிருந்து அகற்ற நிச்சயமாக விரும்புகிறீர்களா?", "1R43+L": "நாஸ்டர் பணப்பை இணைப்புக் கட்டமைப்பை உள்ளிடவும்", "1c4YST": "{node} உடன் இணைக்கப் பட்டது 🎉", "1nYUGC": "{n} பின்தொடரப் படுவோர்", "1o2BgB": "கையெழுத்துக்களை சரிபார்", - "1ozeyg": "Nature", + "1ozeyg": "இயற்கை", "1udzha": "உரையாடல்கள்", "2/2yg+": "சேர்", "25V4l1": "பதாகை", - "25WwxF": "Don't have an account?", + "25WwxF": "கணக்கு இல்லையா?", "2IFGap": "நன்கொடை அளிக்க", "2LbrkB": "கடவுச்சொல்லை உள்ளிடுக", - "2O2sfp": "Finish", + "2O2sfp": "முடிக்க", "2a2YiP": "{n} புத்தகக் குறிகள்", "2k0Cv+": "விருப்பமின்மைகள் ({n})", "2ukA4d": "{n} மணித்துளிகள்", "2zJXeA": "சுயவிவரங்கள்", - "39AHJm": "Sign Up", + "39AHJm": "பதிவு செய்", "3KNMbJ": "கட்டுரைகள்", "3cc4Ct": "ஒளி", "3gOsZq": "மொழிபெயர்ப்பாளர்கள்", @@ -56,7 +58,7 @@ "4IPzdn": "முதன்மை உருவாக்கி", "4L2vUY": "உங்களது புதிய NIP-05 கணக்கு:", "4MBtMa": "பெயர் 1 முதல் 32 எழுத்துக்களைக் கொண்டிருக்க வேண்டும்", - "4MjsHk": "Life", + "4MjsHk": "வாழ்க்கை", "4OB335": "விருப்பமில்லை", "4Vmpt4": "Nostr Plebs முதல் NIP-05 வழங்குநர்களில் ஒன்றாகும். மேலும், இது நியாயமான விலையில் டொமைன்களின் நல்ல தொகுப்பை வழங்குகிறது", "4Z3t5i": "படங்களை சுருக்க imgproxy உபயோகிக்கவும்", @@ -69,7 +71,7 @@ "5ykRmX": "ஜாப் அனுப்பு", "6/SF6e": "

{n}

காஷூ சாட்கள்", "6/hB3S": "மறு ஓட்டத்தைப் பார்க்கவும்", - "62nsdy": "Retry", + "62nsdy": "மீண்டும் முயற்சிக்கவும்", "65BmHb": "{host} இல் இருந்து படத்தைப் பதிலி செய்ய முடியவில்லை, நேரடியாகப் பெற இங்கு கிளிக் செய்யவும்", "6OSOXl": "காரணம்: {reason}", "6TfgXX": "{site} என்பது ஆர்வமுள்ள மக்கள் தங்கள் ஓய்வு நேரத்தில் உருவாக்கிய திறந்த மூல திட்டம் ஆகும்", @@ -79,31 +81,31 @@ "7+Domh": "குறிப்புகள்", "712i26": "Proxy uses HODL invoices to forward the payment, which hides the pubkey of your node", "7BX/yC": "கணக்கு மாற்றி", - "7UOvbT": "Offline", + "7UOvbT": "அகல்நிலை", "7hp70g": "NIP-05", "8/vBbP": "மறுபதிவுகள் ({n})", "89q5wc": "மறுப்பதிவுகளை உறுதி செய்யவும்", "8ED/4u": "இவருக்கு பதிலளி", "8QDesP": "{n} ஸாட்கள் ஜாப் செய்", "8Rkoyb": "பெறுநர்", - "8Y6bZQ": "Invalid zap split: {input}", + "8Y6bZQ": "தவறான zap பிளவு: {input}", "8g2vyB": "பெயர் மிக நீளமாக உள்ளது", "8v1NN+": "ஜோடிக்கும் சொற்றொடர்", "9+Ddtu": "அடுத்து", "9HU8vw": "பதில்", "9SvQep": "பின்தொடர்வுகள் {n}", "9WRlF4": "அனுப்பு", - "9kSari": "Retry publishing", + "9kSari": "வெளியிட மீண்டும் முயற்சிக்கவும்", "9pMqYs": "நாஸ்டர் முகவரி", "9wO4wJ": "லைட்னிங் விலைப்பட்டியல்", "ABAQyo": "அரட்டைகள்", "ADmfQT": "பெற்றோர்", - "AIgmDy": "Add up to 4 hashtags", + "AIgmDy": "4 ஹேஷ்டேக்குகள் வரை சேர்க்கவும்", "AN0Z7Q": "முடக்கப்பட்ட வார்த்தைகள்", "ASRK0S": "இந்தப் பதிவாளர் முடக்கப் பட்டுள்ளார்", "Ai8VHU": "ஸ்நார்ட் ரிலேயில் வரம்பற்ற குறிப்புகளை வைத்திரு", "AkCxS/": "காரணம்", - "Am8glJ": "Game", + "Am8glJ": "விளையாட்டு", "AnLrRC": "ஜாப் அல்லாத", "AxDOiG": "மாதங்கள்", "AyGauy": "உள்நுழை", @@ -114,15 +116,15 @@ "BWpuKl": "புதுப்பி", "BjNwZW": "நாஸ்டர் முகவரி (nip05)", "C1LjMx": "லைட்னிங் நன்கொடை", - "C7642/": "Quote Repost", + "C7642/": "மேற்கோள் மறுபதிவு", "C81/uG": "வெளியேறு", "C8HhVE": "பரிந்துரைக்கப்படும் பயனர்கள்", "CHTbO3": "விலைப்பட்டியலை பெற முடியவில்லை", - "CVWeJ6": "Trending People", + "CVWeJ6": "டிரெண்டிங் நபர்கள்", "CmZ9ls": "{n} ஒலியடக்கப்பட்டவை", - "CsCUYo": "{n} sats", + "CsCUYo": "{n} சாட்ஸ்", "Cu/K85": "{lang} இல் இருந்து மொழிபெயர்க்கப் பட்டது", - "CzHZoc": "Social Graph", + "CzHZoc": "சமூக வரைபடம்", "D+KzKd": "கிடைக்கும் ஒவ்வொரு குறிப்புகளையும் தானாக ஜாப் செய்யவும்", "D3idYv": "அமைப்புகள்", "DBiVK1": "தேக்ககம்", @@ -132,21 +134,21 @@ "Dh3hbq": "தானாக ஜாப்", "Dn82AL": "நேரலை", "DtYelJ": "பரிமாற்றம்", - "Dx4ey3": "Toggle all", + "Dx4ey3": "அனைத்தையும் நிலைமாற்று", "EJbFi7": "குறிப்புகளைத் தேடு", "ELbg9p": "தரவு வழங்குநர்", - "EQKRE4": "Show badges on profile pages", + "EQKRE4": "சுயவிவரப் பக்கங்களில் பேட்ஜ்களைக் காட்டு", "EWyQH5": "முழுதளாவிய", "Ebl/B2": "{lang} இற்கு மொழிபெயர்க்கவும்", - "EcZF24": "Custom Relays", - "EcfIwB": "Username is available", + "EcZF24": "கஸ்டம் ரிலேஸ்", + "EcfIwB": "பயனர் பெயர் இருக்கிறது", "EcglP9": "சாவி", - "EjFyoR": "On-chain Donation Address", + "EjFyoR": "ஆன்-செயின் நன்கொடை முகவரி", "EnCOBJ": "வாங்கு", "F3l7xL": "கணக்கை சேர்", "FDguSC": "{n} ஜாப்கள்", - "FMfjrl": "Show status messages on profile pages", - "FSYL8G": "Trending Users", + "FMfjrl": "சுயவிவரப் பக்கங்களில் நிலை செய்திகளைக் காட்டு", + "FSYL8G": "பிரபலமான பயனர்கள்", "FcNSft": "Redirect issues HTTP redirect to the supplied lightning address", "FdhSU2": "இப்போது உரிமை கோரவும்", "FfYsOb": "ஓரு பிழை நேர்ந்துவிட்டது!", @@ -155,36 +157,36 @@ "G1BGCg": "பணப்பை தேர்வு", "GFOoEE": "உப்பு", "GL8aXW": "புக்மார்க்குகள் ({n})", - "GQPtfk": "Join Stream", - "GSye7T": "Lightning Address", + "GQPtfk": "ஸ்ட்ரீமில் சேரவும்", + "GSye7T": "லைட்னிங் முகவரி", "GUlSVG": "உங்கள் ஸ்நார்ட் நாஸ்டர் முகவரியைப் பெறவும்", "Gcn9NQ": "மேக்னெட் இணைப்பு", "GspYR7": "{n} விருப்பமின்மை", - "Gxcr08": "Broadcast Event", + "Gxcr08": "ஒளிபரப்பு நிகழ்வு", "H+vHiz": "ஹெக்ஸ் சாவி..", "H0JBH6": "வெளியேறு", "H6/kLh": "ஆர்டர் செலுத்தப்பட்டது!", "HAlOn1": "பெயர்", "HFls6j": "பெயர் பின்னர் கிடைக்கப் பெறும்", "HOzFdo": "ஒலியடக்கப்பட்டவை", - "HWbkEK": "Clear cache and reload", + "HWbkEK": "தற்காலிக சேமிப்பை அழித்து மீண்டும் ஏற்றவும்", "HbefNb": "திறந்த பணப்பை", - "HhcAVH": "You don't follow this person, click here to load media from {link}, or update your preferences to always load media from everybody.", + "HhcAVH": "நீங்கள் இவரைப் பின்தொடரவில்லை, மீடியாவை ஏற்ற இங்கே கிளிக் செய்யவும் {link}, அல்லது புதுப்பிக்கவும் உங்கள் விருப்பங்களை எல்லாரிடமிருந்தும் எப்போதும் மீடியாவை ஏற்றுவதற்கு.", "IEwZvs": "இந்தக் குறிப்பின் நிலையான பொறுத்தத்தை நிச்சயமாக நீக்க விரும்புகிறீர்களா?", "IKKHqV": "பின்தொடர்வுகள்", - "IVbtTS": "Zap all {n} sats", - "IWz1ta": "Auto Translate", - "Ig9/a1": "Sent {n} sats to {name}", - "IoQq+a": "Click here to load anyway", - "Ix8l+B": "Trending Notes", + "IVbtTS": "{n} சாட்கள் ஜாப் செய்", + "IWz1ta": "தானியங்கு மொழிபெயர்ப்பு", + "Ig9/a1": "{name} க்கு {n} சாட்ஸ் அனுப்பப்பட்டது", + "IoQq+a": "எப்படியும் ஏற்ற இங்கே கிளிக் செய்யவும்", + "Ix8l+B": "பிரபலமான குறிப்புகள்", "J+dIsA": "சந்தாக்கள்", - "J2HeQ+": "Use commas to separate words e.g. word1, word2, word3", + "J2HeQ+": "சொற்களைப் பிரிக்க காற்புள்ளிகளைப் பயன்படுத்தவும் எ.கா. சொல்1, சொல்2, சொல்3", "JCIgkj": "பயனர் பெயர்", - "JGrt9q": "Send sats to {name}", + "JGrt9q": "{name} க்கு சாட்களை அனுப்பு", "JHEHCk": "ஜாப்கள் ({n})", - "JIVWWA": "Sport", - "JPFYIM": "No lightning address", - "JSx7y9": "Subscribe to {site_name} {plan} for {price} and receive the following rewards", + "JIVWWA": "விளையாட்டு", + "JPFYIM": "லைட்னிங் முகவரி இல்லை", + "JSx7y9": "{price} க்கு {site_name} {plan} இல் குழுசேர்ந்து பின்வரும் வெகுமதிகளைப் பெறுங்கள்", "JeoS4y": "மறுபதிவு", "JjGgXI": "பயனர்களைத் தேடுக", "JkLHGw": "வலைத்தளம்", @@ -192,24 +194,24 @@ "K3r6DQ": "நீக்கு", "K7AkdL": "காண்பி", "KAhAcM": "LNDHub கட்டமைப்பை உள்ளிடவும்", - "KHK8B9": "Relay", + "KHK8B9": "ரிலே", "KQvWvD": "நீக்கப்பட்டது", "KahimY": "அறிந்திராத நிகழ்வு வகை: {kind}", - "KoFlZg": "Enter mint URL", - "KtsyO0": "Enter Pin", + "KoFlZg": "mint URL ஐ உள்ளிடவும்", + "KtsyO0": "பின்னை உள்ளிடவும்", "LF5kYT": "பிற இணைப்புகள்", - "LR1XjT": "Pin too short", + "LR1XjT": "பின் மிகவும் சிறியது", "LXxsbk": "பெயரிலா", "LgbKvU": "கருத்து", - "Lu5/Bj": "Open on Zapstr", - "Lw+I+J": "{n,plural,=0{{name} zapped} other{{name} & {n} others zapped}}", - "LwYmVi": "Zaps on this note will be split to the following users.", + "Lu5/Bj": "Zapstr இல் திறக்கவும்", + "Lw+I+J": "{n,plural,=0{{name} zapped} other{{{name} & {n} others zapped}}", + "LwYmVi": "இந்தக் குறிப்பில் உள்ள Zaps பின்வரும் பயனர்களுக்குப் பிரிக்கப்படும்.", "M3Oirc": "மெனுக்களை பிழை திருத்தவும்", "MBAYRO": "ஒவ்வொரு செய்தியிலும் சூழல் மெனுவில் \"IDஐ நகலெடு\" மற்றும் \"நிகழ்வு JSONஐ நகலெடு\" ஆகியவற்றைக் காட்டுகிறது", "MI2jkA": "கிடைக்கவில்லை:", "MP54GY": "பணப்பை கடவுச்சொல்", "MWTx65": "இயல்புநிலை பக்கம்", - "MiMipu": "Set as primary Nostr address (nip05)", + "MiMipu": "முதன்மை Nostr முகவரியாக அமைக்கவும் (nip05)", "Mrpkot": "சந்தாவுக்கு பணம் செலுத்துங்கள்", "MuVeKe": "Buy nostr address", "MzRYWH": "{item} வாங்கப் படுகிறது", @@ -274,6 +276,7 @@ "Ub+AGc": "Sign In", "Up5U7K": "முடக்கு", "UrKTqQ": "You have an active iris.to account", + "VL900k": "Recommended Relays", "VN0+Fz": "இருப்பு: {amount} ஸாட்கள்", "VOjC1i": "எந்தப் பதிவேற்ற சேவையில் இணைப்புகளைப் பதிவேற்ற விரும்புகிறீர்கள் என்பதைத் தேர்ந்தெடுக்கவும்", "VR5eHw": "பொது சாவி (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "Your {site_name} subscription is expired", "jHa/ko": "Clean up your feed", "jMzO1S": "உள் பிழை: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "பின்", "jvo0vs": "சேமி", "jzgQ2z": "{n} எதிர்வினைகள்", diff --git a/packages/app/src/translations/th_TH.json b/packages/app/src/translations/th_TH.json index 225dfd76..236ca36c 100644 --- a/packages/app/src/translations/th_TH.json +++ b/packages/app/src/translations/th_TH.json @@ -21,11 +21,13 @@ "08zn6O": "ส่งออกกุญแจ", "0Azlrb": "จัดการ", "0BUTMv": "ค้นหา...", + "0HFX0T": "Use Exact Location", "0jOEtS": "LNURL ไม่ถูกต้อง", "0mch2Y": "ชื่อมีอักขระที่ไม่อนุญาตให้ใช้", "0siT4z": "Politics", "0uoY11": "Show Status", "0yO7wF": "{n} วินาที", + "1H4Keq": "{n} users", "1Mo59U": "คุณแน่ใจหรือว่าต้องการลบโน้ตนี้ออกจากบุ๊คมาร์ค?", "1R43+L": "ใส่ Nostr Wallet Connect config", "1c4YST": "เชื่อมต่อกับ: {node} 🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "Sign In", "Up5U7K": "บล็อก", "UrKTqQ": "You have an active iris.to account", + "VL900k": "Recommended Relays", "VN0+Fz": "คงเหลือ: {amount} sats", "VOjC1i": "เลือกบริการอัปโหลดที่คุณต้องการอัปโหลดไฟล์แนบ", "VR5eHw": "Public key (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "Your {site_name} subscription is expired", "jHa/ko": "Clean up your feed", "jMzO1S": "ข้อผิดพลาดภายใน: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "ย้อนกลับ", "jvo0vs": "บันทึก", "jzgQ2z": "{n} Reactions", diff --git a/packages/app/src/translations/zh_CN.json b/packages/app/src/translations/zh_CN.json index f54797c3..7996e29f 100644 --- a/packages/app/src/translations/zh_CN.json +++ b/packages/app/src/translations/zh_CN.json @@ -21,11 +21,13 @@ "08zn6O": "导出密钥", "0Azlrb": "管理", "0BUTMv": "搜索...", + "0HFX0T": "Use Exact Location", "0jOEtS": "LNURL无效", "0mch2Y": "名称中有禁用字符", "0siT4z": "政治", "0uoY11": "显示状态", "0yO7wF": "{n} 秒", + "1H4Keq": "{n} users", "1Mo59U": "是否确定要从收藏中移除此条笔记?", "1R43+L": "输入 Nostr Wallet Connect 配置", "1c4YST": "已连接到:{node}🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "登录", "Up5U7K": "屏蔽", "UrKTqQ": "你有一个活跃的 iris.to 帐户", + "VL900k": "Recommended Relays", "VN0+Fz": "余额: {amount} 聪", "VOjC1i": "选择你要将附件上传到哪个上传服务", "VR5eHw": "公钥 (npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "你的 {site_name} 订阅已过期", "jHa/ko": "清理你的订阅", "jMzO1S": "内部错误: {msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "返回", "jvo0vs": "保存", "jzgQ2z": "{n} 个回应", diff --git a/packages/app/src/translations/zh_TW.json b/packages/app/src/translations/zh_TW.json index 673730dd..710578b5 100644 --- a/packages/app/src/translations/zh_TW.json +++ b/packages/app/src/translations/zh_TW.json @@ -21,11 +21,13 @@ "08zn6O": "導出密鑰", "0Azlrb": "管理", "0BUTMv": "搜索...", + "0HFX0T": "Use Exact Location", "0jOEtS": "LNURL 無效", "0mch2Y": "名稱中有禁用字符", "0siT4z": "政治", "0uoY11": "顯示狀態", "0yO7wF": "{n} 秒", + "1H4Keq": "{n} users", "1Mo59U": "是否確定要從收藏中移除此條筆記?", "1R43+L": "輸入 Nostr Wallet Connect 配置", "1c4YST": "已連接到:{node} 🎉", @@ -274,6 +276,7 @@ "Ub+AGc": "登錄", "Up5U7K": "屏蔽", "UrKTqQ": "你有一個活躍的 iris.to 帳戶", + "VL900k": "Recommended Relays", "VN0+Fz": "餘額:{amount} 聰", "VOjC1i": "選擇你要將附件上傳到哪個上傳服務", "VR5eHw": "公鑰(npub/nprofile)", @@ -381,6 +384,7 @@ "jAmfGl": "你的 {site_name} 訂閱已過期了", "jHa/ko": "清理你的訂閱", "jMzO1S": "內部錯誤:{msg}", + "jTrbGf": "{n} km - {location}", "jfV8Wr": "返回", "jvo0vs": "保存", "jzgQ2z": "{n} 個回應", From eba47f085dfd702da27071eefa90b7bd608526bf Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 17 Nov 2023 20:39:24 +0000 Subject: [PATCH 06/11] fix: preserve tag info in notification context --- packages/app/src/Pages/Notifications.tsx | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/app/src/Pages/Notifications.tsx b/packages/app/src/Pages/Notifications.tsx index 4914c252..e334fdb5 100644 --- a/packages/app/src/Pages/Notifications.tsx +++ b/packages/app/src/Pages/Notifications.tsx @@ -11,7 +11,7 @@ import { Bar, BarChart, Tooltip, XAxis, YAxis } from "recharts"; import useLogin from "Hooks/useLogin"; import { markNotificationsRead } from "Login"; import { Notifications } from "Cache"; -import { dedupe, findTag, orderAscending, orderDescending, getDisplayName } from "SnortUtils"; +import { dedupe, orderAscending, orderDescending, getDisplayName } from "SnortUtils"; import Icon from "Icons/Icon"; import ProfileImage from "Element/User/ProfileImage"; import useModeration from "Hooks/useModeration"; @@ -28,18 +28,17 @@ import { ShowMoreInView } from "Element/Event/ShowMore"; function notificationContext(ev: TaggedNostrEvent) { switch (ev.kind) { case EventKind.ZapReceipt: { - const aTag = findTag(ev, "a"); + const aTag = ev.tags.find(a => a[0] === "a"); if (aTag) { - const [kind, author, d] = aTag.split(":"); - return new NostrLink(NostrPrefix.Address, d, Number(kind), author); + return NostrLink.fromTag(aTag); } - const eTag = findTag(ev, "e"); + const eTag = ev.tags.find(a => a[0] === "e"); if (eTag) { - return new NostrLink(CONFIG.eventLinkPrefix, eTag); + return NostrLink.fromTag(eTag); } - const pTag = ev.tags.filter(a => a[0] === "p").slice(-1)?.[0]; + const pTag = ev.tags.find(a => a[0] === "p"); if (pTag) { - return new NostrLink(NostrPrefix.PublicKey, pTag[1]); + return NostrLink.fromTag(pTag); } break; } @@ -47,17 +46,14 @@ function notificationContext(ev: TaggedNostrEvent) { case EventKind.Reaction: { const thread = EventExt.extractThread(ev); const tag = unwrap(thread?.replyTo ?? thread?.root ?? { value: ev.id, key: "e" }); - if (tag.key === "e") { - return new NostrLink(CONFIG.eventLinkPrefix, unwrap(tag.value)); - } else if (tag.key === "a") { - const [kind, author, d] = unwrap(tag.value).split(":"); - return new NostrLink(NostrPrefix.Address, d, Number(kind), author); + if (tag.key === "e" || tag.key === "a") { + return NostrLink.fromThreadTag(tag); } else { throw new Error("Unknown thread context"); } } case EventKind.TextNote: { - return new NostrLink(NostrPrefix.Note, ev.id); + return NostrLink.fromEvent(ev); } } } From 9721aa506acc3066ac1e9c27a62e385862752344 Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 17 Nov 2023 21:50:12 +0000 Subject: [PATCH 07/11] fix: random exception on mentions --- packages/app/src/Element/Embed/Mention.tsx | 2 +- packages/app/src/Element/Embed/NostrLink.tsx | 4 ++++ packages/shared/src/utils.ts | 20 ++++++-------------- packages/system/src/nostr-link.ts | 9 +++++++-- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/app/src/Element/Embed/Mention.tsx b/packages/app/src/Element/Embed/Mention.tsx index 0c599ba3..219a4221 100644 --- a/packages/app/src/Element/Embed/Mention.tsx +++ b/packages/app/src/Element/Embed/Mention.tsx @@ -14,7 +14,7 @@ export default function Mention({ link }: { link: NostrLink }) { return ( <> - e.stopPropagation()}> + e.stopPropagation()}> @ diff --git a/packages/app/src/Element/Embed/NostrLink.tsx b/packages/app/src/Element/Embed/NostrLink.tsx index 994a7748..becd6f9a 100644 --- a/packages/app/src/Element/Embed/NostrLink.tsx +++ b/packages/app/src/Element/Embed/NostrLink.tsx @@ -8,6 +8,10 @@ export default function NostrLink({ link, depth }: { link: string; depth?: numbe const nav = tryParseNostrLink(link); if (nav?.type === NostrPrefix.PublicKey || nav?.type === NostrPrefix.Profile) { + if(nav.id.startsWith("npub")){ + // eslint-disable-next-line no-debugger + debugger; + } return ; } else if (nav?.type === NostrPrefix.Note || nav?.type === NostrPrefix.Event || nav?.type === NostrPrefix.Address) { if ((depth ?? 0) > 0) { diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index 6976140d..aa075bc7 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -144,13 +144,9 @@ export function getPublicKey(privKey: string) { } export function bech32ToHex(str: string) { - try { - const nKey = bech32.decode(str, 1_000); - const buff = bech32.fromWords(nKey.words); - return utils.bytesToHex(Uint8Array.from(buff)); - } catch (e) { - return str; - } + const nKey = bech32.decode(str, 1_000); + const buff = bech32.fromWords(nKey.words); + return utils.bytesToHex(Uint8Array.from(buff)); } /** @@ -159,13 +155,9 @@ export function bech32ToHex(str: string) { * @returns */ export function bech32ToText(str: string) { - try { - const decoded = bech32.decode(str, 1000); - const buf = bech32.fromWords(decoded.words); - return new TextDecoder().decode(Uint8Array.from(buf)); - } catch { - return ""; - } + const decoded = bech32.decode(str, 1000); + const buf = bech32.fromWords(decoded.words); + return new TextDecoder().decode(Uint8Array.from(buf)); } export async function fetchNip05Pubkey(name: string, domain: string, timeout = 2_000): Promise { diff --git a/packages/system/src/nostr-link.ts b/packages/system/src/nostr-link.ts index ee47ad26..9c24bf8b 100644 --- a/packages/system/src/nostr-link.ts +++ b/packages/system/src/nostr-link.ts @@ -233,11 +233,16 @@ export function tryParseNostrLink(link: string, prefixHint?: NostrPrefix): Nostr } } -export function parseNostrLink(link: string, prefixHint?: NostrPrefix): NostrLink { - let entity = link.startsWith("web+nostr:") || link.startsWith("nostr:") ? link.split(":")[1] : link; +export function trimNostrLink(link: string) { + let entity = (link.startsWith("web+nostr:") || link.startsWith("nostr:")) ? link.split(":")[1] : link; // trim any non-bech32 chars entity = entity.match(/(n(?:pub|profile|event|ote|addr|req)1[acdefghjklmnpqrstuvwxyz023456789]+)/)?.[0] ?? entity; + return entity; +} + +export function parseNostrLink(link: string, prefixHint?: NostrPrefix): NostrLink { + const entity = trimNostrLink(link); const isPrefix = (prefix: NostrPrefix) => { return entity.startsWith(prefix); From e3cb397d1d0fad3ebaba26e2a3bf2b44574c7c2a Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 17 Nov 2023 21:52:03 +0000 Subject: [PATCH 08/11] chore: Update translations --- packages/app/src/Element/Embed/NostrLink.tsx | 2 +- packages/system/src/nostr-link.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/src/Element/Embed/NostrLink.tsx b/packages/app/src/Element/Embed/NostrLink.tsx index becd6f9a..ca7fb89d 100644 --- a/packages/app/src/Element/Embed/NostrLink.tsx +++ b/packages/app/src/Element/Embed/NostrLink.tsx @@ -8,7 +8,7 @@ export default function NostrLink({ link, depth }: { link: string; depth?: numbe const nav = tryParseNostrLink(link); if (nav?.type === NostrPrefix.PublicKey || nav?.type === NostrPrefix.Profile) { - if(nav.id.startsWith("npub")){ + if (nav.id.startsWith("npub")) { // eslint-disable-next-line no-debugger debugger; } diff --git a/packages/system/src/nostr-link.ts b/packages/system/src/nostr-link.ts index 9c24bf8b..6e53c657 100644 --- a/packages/system/src/nostr-link.ts +++ b/packages/system/src/nostr-link.ts @@ -234,7 +234,7 @@ export function tryParseNostrLink(link: string, prefixHint?: NostrPrefix): Nostr } export function trimNostrLink(link: string) { - let entity = (link.startsWith("web+nostr:") || link.startsWith("nostr:")) ? link.split(":")[1] : link; + let entity = link.startsWith("web+nostr:") || link.startsWith("nostr:") ? link.split(":")[1] : link; // trim any non-bech32 chars entity = entity.match(/(n(?:pub|profile|event|ote|addr|req)1[acdefghjklmnpqrstuvwxyz023456789]+)/)?.[0] ?? entity; From f2ff2c65be2bdbc9af5b2921aa37e687b2c1c61c Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 17 Nov 2023 22:21:24 +0000 Subject: [PATCH 09/11] feat: use blasters for new user metadata --- packages/app/src/Const.ts | 5 +++++ packages/app/src/Login/Functions.ts | 4 +++- packages/app/src/Pages/settings/Relays.tsx | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/app/src/Const.ts b/packages/app/src/Const.ts index 634aa8d0..2984872a 100644 --- a/packages/app/src/Const.ts +++ b/packages/app/src/Const.ts @@ -59,6 +59,11 @@ export const DefaultImgProxy = { */ export const DerivationPath = "m/44'/1237'/0'/0/0"; +/** + * Blaster relays + */ +export const Blasters = ["wss://nostr.mutinywallet.com"]; + /** * Regex to match email address */ diff --git a/packages/app/src/Login/Functions.ts b/packages/app/src/Login/Functions.ts index 21a2bd42..dbbf16c3 100644 --- a/packages/app/src/Login/Functions.ts +++ b/packages/app/src/Login/Functions.ts @@ -12,7 +12,7 @@ import { unixNowMs } from "@snort/shared"; import * as secp from "@noble/curves/secp256k1"; import * as utils from "@noble/curves/abstract/utils"; -import { SnortPubKey } from "Const"; +import { Blasters, SnortPubKey } from "Const"; import { LoginStore, UserPreferences, LoginSession, LoginSessionType, SnortAppData, Newest } from "Login"; import { generateBip39Entropy, entropyToPrivateKey } from "nip6"; import { bech32ToHex, dedupeById, getCountry, sanitizeRelayUrl, unwrap } from "SnortUtils"; @@ -123,10 +123,12 @@ export async function generateNewLogin( // Create relay metadata event const ev2 = await publisher.relayList(newRelays); await system.BroadcastEvent(ev2); + await Promise.all(Blasters.map(a => system.WriteOnceToRelay(a, ev2))); // Publish new profile const ev3 = await publisher.metadata(profile); await system.BroadcastEvent(ev3); + await Promise.all(Blasters.map(a => system.WriteOnceToRelay(a, ev3))); LoginStore.loginWithPrivateKey(await pin(privateKey), entropy, newRelays); } diff --git a/packages/app/src/Pages/settings/Relays.tsx b/packages/app/src/Pages/settings/Relays.tsx index a3b8eb8e..14d2316b 100644 --- a/packages/app/src/Pages/settings/Relays.tsx +++ b/packages/app/src/Pages/settings/Relays.tsx @@ -11,9 +11,9 @@ import AsyncButton from "Element/AsyncButton"; import SnortApi, { RelayDistance } from "External/SnortApi"; import { getCountry, getRelayName, sanitizeRelayUrl } from "SnortUtils"; import { formatShort } from "Number"; +import { Blasters } from "Const"; import messages from "./messages"; -const Blasters = ["wss://nostr.mutinywallet.com"]; export async function saveRelays( system: SystemInterface, From 8deec37d1511c4d47decb0511549264191e3c754 Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 20 Nov 2023 10:39:58 +0000 Subject: [PATCH 10/11] chore: update readme --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 99315420..c7888b96 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Snort supports the following NIP's: - [x] NIP-25: Reactions - [x] NIP-26: Delegated Event Signing (Display delegated signings only) - [x] NIP-27: Text note references -- [ ] NIP-28: Public Chat +- [x] NIP-28: Public Chat - [x] NIP-30: Custom Emoji - [x] NIP-31: Alt tag for unknown events - [x] NIP-36: Sensitive Content @@ -43,7 +43,7 @@ Snort supports the following NIP's: - [x] NIP-59: Gift Wrap - [x] NIP-65: Relay List Metadata - [x] NIP-75: Zap Goals -- [ ] NIP-78: App specific data +- [x] NIP-78: App specific data - [ ] NIP-89: App handlers - [x] NIP-94: File Metadata - [x] NIP-98: HTTP Auth @@ -58,7 +58,7 @@ To run the application, use $ yarn start ``` -To build the application and nostr package, use +To build the application and system packages, use ``` $ yarn build @@ -73,8 +73,7 @@ Translations are managed on [Crowdin](https://crowdin.com/project/snort) To extract translations run: ```bash -yarn workspace @snort/app intl-extract -yarn workspace @snort/app intl-compile +yarn pre:commit ``` This will create the source file `packages/app/src/translations/en.json` From 38277c3252362f042b70048cdbd4d5840e9c387e Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 20 Nov 2023 10:42:06 +0000 Subject: [PATCH 11/11] chore: Update translations --- packages/app/src/translations/de_DE.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/app/src/translations/de_DE.json b/packages/app/src/translations/de_DE.json index bd40d121..6d3578d3 100644 --- a/packages/app/src/translations/de_DE.json +++ b/packages/app/src/translations/de_DE.json @@ -21,13 +21,13 @@ "08zn6O": "Schlüssel exportieren", "0Azlrb": "Verwalten", "0BUTMv": "Suche...", - "0HFX0T": "Use Exact Location", + "0HFX0T": "Exakten Standort verwenden", "0jOEtS": "Ungültige LNURL", "0mch2Y": "Der Name enthält unerlaubte Zeichen", "0siT4z": "Politik", "0uoY11": "Status anzeigen", "0yO7wF": "{n} Sek.", - "1H4Keq": "{n} users", + "1H4Keq": "{n} Benutzer", "1Mo59U": "Bist du sicher, dass du diese Note aus deinen Lesezeichen entfernen möchtest?", "1R43+L": "Nostr Wallet Connect Konfiguration eingeben", "1c4YST": "Verbunden mit: {node}🎉", @@ -276,7 +276,7 @@ "Ub+AGc": "Anmelden", "Up5U7K": "Blockieren", "UrKTqQ": "Du hast ein aktives iris.to Konto", - "VL900k": "Recommended Relays", + "VL900k": "Empfohlene Relais", "VN0+Fz": "Guthaben: {amount} Sats", "VOjC1i": "Wähle einen Upload-Dienst für deine Anhänge", "VR5eHw": "Öffentlicher Schlüssel (npub/nprofile)",