diff --git a/app/src/lib/nostr/loaders.loadTranslations.test.ts b/app/src/lib/nostr/loaders.loadTranslations.test.ts new file mode 100644 index 0000000..eb4578c --- /dev/null +++ b/app/src/lib/nostr/loaders.loadTranslations.test.ts @@ -0,0 +1,74 @@ +import { describe, it, expect } from 'vitest'; +import { resolveTranslationsFromRefs } from './loaders'; +import type { NostrEvent } from './loaders'; +import type { TranslationRef } from './translations'; + +function ev(tags: string[][]): NostrEvent { + return { + id: 'x', + pubkey: 'p', + created_at: 0, + kind: 30023, + tags, + content: '', + sig: 's' + } as unknown as NostrEvent; +} + +describe('resolveTranslationsFromRefs', () => { + it('liefert lang/slug/title für jeden aufgelösten ref', async () => { + const refs: TranslationRef[] = [ + { kind: 30023, pubkey: 'p1', dtag: 'hello' } + ]; + const fetcher = async () => [ + ev([ + ['d', 'hello'], + ['title', 'Hello World'], + ['L', 'ISO-639-1'], + ['l', 'en', 'ISO-639-1'] + ]) + ]; + const result = await resolveTranslationsFromRefs(refs, fetcher); + expect(result).toEqual([ + { lang: 'en', slug: 'hello', title: 'Hello World' } + ]); + }); + + it('ignoriert refs, zu denen kein event gefunden wird', async () => { + const refs: TranslationRef[] = [ + { kind: 30023, pubkey: 'p1', dtag: 'hello' }, + { kind: 30023, pubkey: 'p1', dtag: 'missing' } + ]; + const fetcher = async (r: TranslationRef) => + r.dtag === 'hello' + ? [ev([ + ['d', 'hello'], + ['title', 'Hi'], + ['l', 'en', 'ISO-639-1'] + ])] + : []; + const result = await resolveTranslationsFromRefs(refs, fetcher); + expect(result).toEqual([{ lang: 'en', slug: 'hello', title: 'Hi' }]); + }); + + it('ignoriert events ohne l-tag (sprache unklar)', async () => { + const refs: TranslationRef[] = [ + { kind: 30023, pubkey: 'p', dtag: 'x' } + ]; + const fetcher = async () => [ + ev([ + ['d', 'x'], + ['title', 'kein lang-tag'] + ]) + ]; + const result = await resolveTranslationsFromRefs(refs, fetcher); + expect(result).toEqual([]); + }); + + it('leere ref-liste → leere ergebnis-liste', async () => { + const fetcher = async () => { + throw new Error('should not be called'); + }; + expect(await resolveTranslationsFromRefs([], fetcher)).toEqual([]); + }); +}); diff --git a/app/src/lib/nostr/loaders.ts b/app/src/lib/nostr/loaders.ts index 1fc64d0..15961eb 100644 --- a/app/src/lib/nostr/loaders.ts +++ b/app/src/lib/nostr/loaders.ts @@ -6,6 +6,7 @@ import type { Filter as ApplesauceFilter } from 'applesauce-core/helpers/filter' import { pool } from './pool'; import { readRelays } from '$lib/stores/readRelays'; import { AUTHOR_PUBKEY_HEX, RELAY_HARD_TIMEOUT_MS } from './config'; +import type { TranslationRef } from './translations'; /** Re-export als sprechenden Alias */ export type { NostrEvent }; @@ -189,3 +190,55 @@ export async function loadReactions(dtag: string): Promise { .map(([content, count]) => ({ content, count })) .sort((a, b) => b.count - a.count); } + +export interface TranslationInfo { + lang: string; + slug: string; + title: string; +} + +/** + * Pure Variante für Tests — erhält die Events via Fetcher statt Relays. + */ +export async function resolveTranslationsFromRefs( + refs: TranslationRef[], + fetcher: (ref: TranslationRef) => Promise +): Promise { + if (refs.length === 0) return []; + const results = await Promise.all(refs.map(fetcher)); + const infos: TranslationInfo[] = []; + for (let i = 0; i < refs.length; i++) { + const evs = results[i]; + if (evs.length === 0) continue; + const latest = evs.reduce((best, cur) => + cur.created_at > best.created_at ? cur : best + ); + const lang = latest.tags.find((t) => t[0] === 'l')?.[1]; + if (!lang) continue; + const slug = latest.tags.find((t) => t[0] === 'd')?.[1] ?? refs[i].dtag; + const title = latest.tags.find((t) => t[0] === 'title')?.[1] ?? ''; + infos.push({ lang, slug, title }); + } + return infos; +} + +/** + * Loader: findet die anderssprachigen Varianten eines Posts. + * Liefert leere Liste, wenn keine a-Tags mit marker "translation" vorhanden. + */ +export async function loadTranslations( + event: NostrEvent +): Promise { + const { parseTranslationRefs } = await import('./translations'); + const refs = parseTranslationRefs(event); + if (refs.length === 0) return []; + const relays = get(readRelays); + return resolveTranslationsFromRefs(refs, (ref) => + collectEvents(relays, { + kinds: [ref.kind], + authors: [ref.pubkey], + '#d': [ref.dtag], + limit: 1 + }) + ); +}