Files
eranos/src/components/ReplyContext.tsx
T
mkfain c74dbb6a3b i18n: translate Feed, FeedModeSwitcher, FeedEmptyState, ReplyContext, PostActionBar, ReactionButton
Extends the feed.* namespace with five new sub-namespaces:
compose.placeholder, tabs (Follows / Following / Global), empty (the
six per-tab + per-mode empty-state messages plus the three CTA labels
that show under them), modeSwitcher (the home feed's mode-picker dropdown
with Agora / All Nostr / Following options, the dropdown trigger's aria
label, and the disabled-Following tooltip), replyContext (Replying to,
the and-joiner, plus a pluralized andOthers_one/andOthers_other), and
actions (Reply / Repost / Undo repost / React / Zap / Share / More + the
Link-copied toast).

PostActionBar's replyLabel prop default went from 'Reply' to undefined;
the component falls back to t('feed.actions.reply') at render time so
existing call sites that pass an already-translated string keep working
unchanged. FeedModeSwitcher's OPTIONS array swapped its hard-coded
label string for an i18nKey suffix, materialized through t() inside the
component.

ar / es / zh / fa / ps / km / sn translations drafted inline; km, sn,
ps, fa still pending native-speaker review.
2026-05-23 19:05:18 -05:00

99 lines
3.6 KiB
TypeScript

import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { EmbeddedNote } from '@/components/EmbeddedNote';
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
import { Skeleton } from '@/components/ui/skeleton';
import { ProfileHoverCard } from '@/components/ProfileHoverCard';
import { useAuthor } from '@/hooks/useAuthor';
import { genUserName } from '@/lib/genUserName';
import { useProfileUrl } from '@/hooks/useProfileUrl';
interface ReplyContextProps {
pubkeys: string[];
/** Hex event ID of the parent post being replied to. */
parentEventId?: string;
/** Relay URL hint for fetching the parent event. */
parentRelayHint?: string;
/** Author pubkey hint for NIP-65 outbox resolution of the parent event. */
parentAuthorHint?: string;
className?: string;
}
/**
* Displays "Replying to @username" or "Replying to @user1 and @user2" context for reply posts.
* When parentEventId is provided, hovering over the line shows an embedded preview of the parent post.
* Used consistently across NoteCard and notification views.
*/
export function ReplyContext({ pubkeys, parentEventId, parentRelayHint, parentAuthorHint, className }: ReplyContextProps) {
const { t } = useTranslation();
// Filter out any undefined/empty pubkeys defensively
const validPubkeys = pubkeys.filter(Boolean);
// Show max 2 authors for cleaner UI
const displayPubkeys = validPubkeys.slice(0, 2);
const replyingToLabel = parentEventId ? (
<HoverCard openDelay={300} closeDelay={150}>
<HoverCardTrigger asChild>
<span className="shrink-0 cursor-pointer hover:underline">{t('feed.replyContext.replyingTo')}</span>
</HoverCardTrigger>
<HoverCardContent
side="bottom"
align="start"
sideOffset={4}
className="w-80 p-0 rounded-2xl shadow-lg"
onClick={(e) => e.stopPropagation()}
>
<EmbeddedNote
eventId={parentEventId}
relays={parentRelayHint ? [parentRelayHint] : undefined}
authorHint={parentAuthorHint}
className="border-0 rounded-none"
disableHoverCards
/>
</HoverCardContent>
</HoverCard>
) : (
<span className="shrink-0">{t('feed.replyContext.replyingTo')}</span>
);
return (
<div className={className || 'flex items-center flex-wrap gap-x-1 text-sm text-muted-foreground mt-2 mb-1 min-w-0 overflow-hidden'}>
{replyingToLabel}
{displayPubkeys.map((pubkey, index) => (
<span key={pubkey} className="inline-flex items-center gap-1 min-w-0">
<ReplyAuthor pubkey={pubkey} />
{index < displayPubkeys.length - 1 && <span className="shrink-0">{t('feed.replyContext.and')}</span>}
</span>
))}
{validPubkeys.length > 2 && (
<span className="text-muted-foreground shrink-0">
{t('feed.replyContext.andOthers', { count: validPubkeys.length - 2 })}
</span>
)}
</div>
);
}
function ReplyAuthor({ pubkey }: { pubkey: string }) {
const author = useAuthor(pubkey);
const name = author.data?.metadata?.name || author.data?.metadata?.display_name || genUserName(pubkey);
const profileUrl = useProfileUrl(pubkey, author.data?.metadata);
if (author.isLoading) {
return <Skeleton className="h-3.5 w-20 inline-block" />;
}
return (
<ProfileHoverCard pubkey={pubkey} asChild>
<Link
to={profileUrl}
className="text-primary hover:underline truncate max-w-[200px] inline-block align-bottom"
onClick={(e) => e.stopPropagation()}
>
@{name}
</Link>
</ProfileHoverCard>
);
}