feat(app): loadTranslations liefert sprach-varianten eines posts
This commit is contained in:
parent
8f4125fcc9
commit
7f48644dfc
|
|
@ -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([]);
|
||||
});
|
||||
});
|
||||
|
|
@ -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<ReactionSummary[]> {
|
|||
.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<NostrEvent[]>
|
||||
): Promise<TranslationInfo[]> {
|
||||
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<TranslationInfo[]> {
|
||||
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
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue