Mixed-language UI was confusing — onboarding said "Why DChain / How it
works / Your keys" in English headings but feature descriptions and
CTAs were in Russian; compose's confirm dialog was Russian; feed tabs
were Russian; error messages in humanizeTxError were Russian.
Everything user-facing is now English.
Files touched (only string literals, not comments):
app/index.tsx onboarding slides + CTA buttons
app/(app)/compose.tsx composer alerts, header button, placeholder,
attachment-size hint
app/(app)/feed/index.tsx tab labels (Following/For you/Trending),
empty-state hints, retry button
app/(app)/feed/[id].tsx post detail header + stats rows (Views,
Likes, Size, Paid to publish, Hosted on,
Hashtags)
app/(app)/feed/tag/[tag].tsx empty-state copy
app/(app)/profile/[address].tsx Profile header, Follow/Following,
Edit, Open chat, Address, Copied, Encryption,
Added, Members, unknown-contact hint
app/(app)/new-contact.tsx Search title, placeholder, Search button,
empty-state hint, E2E-ready indicator,
Intro label + placeholder, fee-tier labels
(Min / Standard / Priority), Send request,
Insufficient-balance alert, Request-sent
alert
app/(app)/requests.tsx Notifications title, empty-state, Accept /
Decline buttons, decline-confirm alert,
"wants to add you" line
components/SearchBar.tsx default placeholder
components/feed/PostCard.tsx long-press menu (Delete post, confirm,
Actions / Cancel)
components/feed/ShareSheet.tsx sheet title, contact-search placeholder,
empty state, Select contacts / Send button,
plural helper rewritten for English
components/chat/PostRefCard.tsx "POST" ribbon, "photo" indicator
lib/api.ts humanizeTxError (rate-limit, clock skew,
bad signature, 400/5xx/network-error
messages)
lib/dates.ts dateBucket now returns Today/Yesterday/
"Jun 17, 2025"; month array switched to
English short forms
Code comments left in Russian intentionally — they're developer
context, not user-facing. This commit is purely display-string.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
144 lines
4.3 KiB
TypeScript
144 lines
4.3 KiB
TypeScript
/**
|
||
* PostRefCard — renders a shared feed post inside a chat bubble.
|
||
*
|
||
* Visually distinct from plain messages so the user sees at-a-glance
|
||
* that this came from the feed, not a direct-typed text. Matches
|
||
* VK's "shared wall post" embed pattern:
|
||
*
|
||
* [newspaper icon] ПОСТ
|
||
* @author · 2 строки excerpt'а
|
||
* [📷 Фото in this post]
|
||
*
|
||
* Tap → /(app)/feed/{postID}. The full post (with image + stats +
|
||
* like button) is displayed in the standard post-detail screen.
|
||
*/
|
||
import React from 'react';
|
||
import { View, Text, Pressable } from 'react-native';
|
||
import { Ionicons } from '@expo/vector-icons';
|
||
import { router } from 'expo-router';
|
||
|
||
import { useStore } from '@/lib/store';
|
||
import { Avatar } from '@/components/Avatar';
|
||
|
||
export interface PostRefCardProps {
|
||
postID: string;
|
||
author: string;
|
||
excerpt: string;
|
||
hasImage?: boolean;
|
||
/** True when the card appears inside the sender's own bubble (our own
|
||
* share). Adjusts colour contrast so it reads on the blue bubble
|
||
* background. */
|
||
own: boolean;
|
||
}
|
||
|
||
function shortAddr(a: string, n = 6): string {
|
||
if (!a) return '—';
|
||
return a.length <= n * 2 + 1 ? a : `${a.slice(0, n)}…${a.slice(-n)}`;
|
||
}
|
||
|
||
export function PostRefCard({ postID, author, excerpt, hasImage, own }: PostRefCardProps) {
|
||
const contacts = useStore(s => s.contacts);
|
||
|
||
// Resolve author name the same way the feed does.
|
||
const contact = contacts.find(c => c.address === author);
|
||
const displayName = contact?.username
|
||
? `@${contact.username}`
|
||
: contact?.alias ?? shortAddr(author);
|
||
|
||
const onOpen = () => {
|
||
router.push(`/(app)/feed/${postID}` as never);
|
||
};
|
||
|
||
// Tinted palette based on bubble side — inside an "own" (blue) bubble
|
||
// the card uses a deeper blue so it reads as a distinct nested block,
|
||
// otherwise we use the standard card colours.
|
||
const bg = own ? 'rgba(0, 0, 0, 0.22)' : '#0a0a0a';
|
||
const border = own ? 'rgba(255, 255, 255, 0.15)' : '#1f1f1f';
|
||
const labelColor = own ? 'rgba(255, 255, 255, 0.75)' : '#1d9bf0';
|
||
const bodyColor = own ? '#ffffff' : '#ffffff';
|
||
const subColor = own ? 'rgba(255, 255, 255, 0.65)' : '#8b8b8b';
|
||
|
||
return (
|
||
<Pressable
|
||
onPress={onOpen}
|
||
style={({ pressed }) => ({
|
||
marginBottom: 6,
|
||
borderRadius: 14,
|
||
backgroundColor: pressed ? 'rgba(0,0,0,0.35)' : bg,
|
||
borderWidth: 1,
|
||
borderColor: border,
|
||
overflow: 'hidden',
|
||
})}
|
||
>
|
||
{/* Top ribbon: "ПОСТ" label — makes the shared nature unmistakable. */}
|
||
<View
|
||
style={{
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
gap: 6,
|
||
paddingHorizontal: 10,
|
||
paddingTop: 8,
|
||
paddingBottom: 4,
|
||
}}
|
||
>
|
||
<Ionicons name="newspaper-outline" size={11} color={labelColor} />
|
||
<Text
|
||
style={{
|
||
color: labelColor,
|
||
fontSize: 10,
|
||
fontWeight: '700',
|
||
letterSpacing: 1.2,
|
||
}}
|
||
>
|
||
POST
|
||
</Text>
|
||
</View>
|
||
|
||
{/* Author + excerpt */}
|
||
<View style={{ flexDirection: 'row', paddingHorizontal: 10, paddingBottom: 10 }}>
|
||
<Avatar name={displayName} address={author} size={28} />
|
||
<View style={{ flex: 1, marginLeft: 8, minWidth: 0, overflow: 'hidden' }}>
|
||
<Text
|
||
numberOfLines={1}
|
||
style={{
|
||
color: bodyColor,
|
||
fontWeight: '700',
|
||
fontSize: 13,
|
||
}}
|
||
>
|
||
{displayName}
|
||
</Text>
|
||
{excerpt.length > 0 && (
|
||
<Text
|
||
numberOfLines={3}
|
||
style={{
|
||
color: subColor,
|
||
fontSize: 12,
|
||
lineHeight: 16,
|
||
marginTop: 2,
|
||
}}
|
||
>
|
||
{excerpt}
|
||
</Text>
|
||
)}
|
||
{hasImage && (
|
||
<View
|
||
style={{
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
gap: 4,
|
||
marginTop: 6,
|
||
}}
|
||
>
|
||
<Ionicons name="image-outline" size={11} color={subColor} />
|
||
<Text style={{ color: subColor, fontSize: 11 }}>
|
||
photo
|
||
</Text>
|
||
</View>
|
||
)}
|
||
</View>
|
||
</View>
|
||
</Pressable>
|
||
);
|
||
}
|