mirror of
https://github.com/danog/telegram-tt.git
synced 2025-01-22 05:11:55 +01:00
Localization: Add many translations (#1103)
This commit is contained in:
parent
d2a10a5b8b
commit
ffa338d667
@ -18,6 +18,7 @@ import { buildCollectionByKey } from '../../../util/iteratees';
|
||||
import localDb from '../localDb';
|
||||
|
||||
const MAX_INT_32 = 2 ** 31 - 1;
|
||||
const BETA_LANG_CODES = ['ar', 'fa'];
|
||||
|
||||
export function updateProfile({
|
||||
firstName,
|
||||
@ -250,7 +251,10 @@ export async function fetchLanguages(): Promise<ApiLanguage[] | undefined> {
|
||||
|
||||
export async function fetchLangPack({ sourceLangPacks, langCode }: { sourceLangPacks: string[]; langCode: string }) {
|
||||
const results = await Promise.all(sourceLangPacks.map((langPack) => {
|
||||
return invokeRequest(new GramJs.langpack.GetLangPack({ langPack, langCode }));
|
||||
return invokeRequest(new GramJs.langpack.GetLangPack({
|
||||
langPack,
|
||||
langCode: BETA_LANG_CODES.includes(langCode) ? `${langCode}-raw` : langCode,
|
||||
}));
|
||||
}));
|
||||
|
||||
const collections = results
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
} from '../../modules/helpers';
|
||||
import { pick } from '../../util/iteratees';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import renderText from './helpers/renderText';
|
||||
|
||||
import Avatar from './Avatar';
|
||||
import Modal from '../ui/Modal';
|
||||
@ -96,55 +97,52 @@ const DeleteChatModal: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
chat={chat}
|
||||
isSavedMessages={isChatWithSelf}
|
||||
/>
|
||||
<h3 className="modal-title">{renderTitle()}</h3>
|
||||
<h3 className="modal-title">{lang(renderTitle())}</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderTitle() {
|
||||
if (isChannel && !chat.isCreator) {
|
||||
return 'Leave Channel?';
|
||||
return 'LeaveChannel';
|
||||
}
|
||||
|
||||
if (isChannel && chat.isCreator) {
|
||||
return 'Delete and Leave Channel?';
|
||||
return 'ChannelDelete';
|
||||
}
|
||||
|
||||
if (isBasicGroup || isSuperGroup) {
|
||||
return 'Leave Group?';
|
||||
return 'Group.LeaveGroup';
|
||||
}
|
||||
|
||||
return 'Delete Chat?';
|
||||
return 'DeleteChatUser';
|
||||
}
|
||||
|
||||
function renderMessage() {
|
||||
if (isChannel && !chat.isCreator) {
|
||||
return <p>Are you sure you want to leave channel <strong>{chatTitle}</strong>?</p>;
|
||||
}
|
||||
if (isChannel && chat.isCreator) {
|
||||
return <p>Are you sure you want to delete and leave channel <strong>{chatTitle}</strong>?</p>;
|
||||
return <p>{renderText(lang('ChatList.DeleteAndLeaveGroupConfirmation', chatTitle), ['simple_markdown'])}</p>;
|
||||
}
|
||||
|
||||
if (isBasicGroup || isSuperGroup) {
|
||||
return <p>Are you sure you want to leave group <strong>{chatTitle}</strong>?</p>;
|
||||
if ((isChannel && !chat.isCreator) || isBasicGroup || isSuperGroup) {
|
||||
return <p>{renderText(lang('ChannelLeaveAlertWithName', chatTitle), ['simple_markdown'])}</p>;
|
||||
}
|
||||
|
||||
return <p>Are you sure you want to delete chat with <strong>{contactName}</strong>?</p>;
|
||||
return <p>{renderText(lang('ChatList.DeleteChatConfirmation', contactName), ['simple_markdown'])}</p>;
|
||||
}
|
||||
|
||||
function renderActionText() {
|
||||
if (isChannel && !chat.isCreator) {
|
||||
return 'Leave Channel';
|
||||
return 'LeaveChannel';
|
||||
}
|
||||
if (isChannel && chat.isCreator) {
|
||||
return 'Delete and Leave Channel';
|
||||
return 'Chat.Input.Delete';
|
||||
}
|
||||
|
||||
if (isBasicGroup || isSuperGroup) {
|
||||
return 'Leave Group';
|
||||
return 'Group.LeaveGroup';
|
||||
}
|
||||
|
||||
return `Delete${canDeleteForAll ? ' just for me' : ''}`;
|
||||
return canDeleteForAll ? 'ChatList.DeleteForCurrentUser' : 'Delete';
|
||||
}
|
||||
|
||||
return (
|
||||
@ -157,11 +155,11 @@ const DeleteChatModal: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
{renderMessage()}
|
||||
{canDeleteForAll && (
|
||||
<Button color="danger" className="confirm-dialog-button" isText onClick={handleDeleteMessageForAll}>
|
||||
Delete for {contactName ? `me and ${contactName}` : 'Everyone'}
|
||||
{contactName ? lang('ChatList.DeleteForEveryone', contactName) : lang('DeleteForAll')}
|
||||
</Button>
|
||||
)}
|
||||
<Button color="danger" className="confirm-dialog-button" isText onClick={handleDeleteChat}>
|
||||
{renderActionText()}
|
||||
{lang(renderActionText())}
|
||||
</Button>
|
||||
<Button className="confirm-dialog-button" isText onClick={onClose}>{lang('Cancel')}</Button>
|
||||
</Modal>
|
||||
|
@ -91,19 +91,19 @@ const DeleteMessageModal: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
>
|
||||
<p>{lang('AreYouSureDeleteSingleMessage')}</p>
|
||||
{willDeleteForCurrentUserOnly && (
|
||||
<p>This will delete it just for you, not for other participants in the chat.</p>
|
||||
<p>{lang('lng_delete_for_me_chat_hint')}</p>
|
||||
)}
|
||||
{willDeleteForAll && (
|
||||
<p>This will delete it for everyone in this chat.</p>
|
||||
<p>{lang('lng_delete_for_everyone_hint')}</p>
|
||||
)}
|
||||
{canDeleteForAll && (
|
||||
<Button color="danger" className="confirm-dialog-button" isText onClick={handleDeleteMessageForAll}>
|
||||
Delete for {contactName ? 'me and ' : 'Everyone'}
|
||||
{contactName && renderText(contactName)}
|
||||
{contactName && lang('Conversation.DeleteMessagesFor', renderText(contactName))}
|
||||
{!contactName && lang('Conversation.DeleteMessagesForEveryone')}
|
||||
</Button>
|
||||
)}
|
||||
<Button color="danger" className="confirm-dialog-button" isText onClick={handleDeleteMessageForSelf}>
|
||||
Delete{canDeleteForAll ? ' just for me' : ''}
|
||||
{lang(canDeleteForAll ? 'ChatList.DeleteForCurrentUser' : 'Delete')}
|
||||
</Button>
|
||||
<Button className="confirm-dialog-button" isText onClick={onClose}>{lang('Cancel')}</Button>
|
||||
</Modal>
|
||||
|
@ -49,7 +49,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
|
||||
const pictogramId = message && `sticker-reply-thumb${message.id}`;
|
||||
const mediaThumbnail = useWebpThumbnail(message);
|
||||
|
||||
useLang();
|
||||
const lang = useLang();
|
||||
|
||||
const senderTitle = sender && getSenderTitle(sender);
|
||||
|
||||
@ -68,7 +68,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
|
||||
) : isActionMessage(message) ? (
|
||||
<ActionMessage message={message} isEmbedded />
|
||||
) : (
|
||||
renderText(getMessageSummaryText(message, Boolean(mediaThumbnail)))
|
||||
renderText(getMessageSummaryText(lang, message, Boolean(mediaThumbnail)))
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -4,7 +4,19 @@
|
||||
justify-content: center;
|
||||
color: var(--color-text-meta);
|
||||
|
||||
&.with-description {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.AnimatedSticker {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: .875rem;
|
||||
text-align: center;
|
||||
margin: 1rem 0 0;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
}
|
||||
|
@ -2,21 +2,26 @@ import React, { FC, memo } from '../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
import renderText from './helpers/renderText';
|
||||
import useLang from '../../hooks/useLang';
|
||||
|
||||
import './NothingFound.scss';
|
||||
|
||||
interface OwnProps {
|
||||
text?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
const DEFAULT_TEXT = 'Nothing found.';
|
||||
|
||||
const NothingFound: FC<OwnProps> = ({ text = DEFAULT_TEXT }) => {
|
||||
const NothingFound: FC<OwnProps> = ({ text = DEFAULT_TEXT, description }) => {
|
||||
const lang = useLang();
|
||||
const { transitionClassNames } = useShowTransition(true);
|
||||
|
||||
return (
|
||||
<div className={buildClassName('NothingFound', transitionClassNames)}>
|
||||
<div className={buildClassName('NothingFound', transitionClassNames, description && 'with-description')}>
|
||||
{text}
|
||||
{description && <p className="description">{renderText(lang(description), ['br'])}</p>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -10,6 +10,7 @@ import { STICKER_SIZE_MODAL } from '../../config';
|
||||
import { pick } from '../../util/iteratees';
|
||||
import { selectStickerSet } from '../../modules/selectors';
|
||||
import { useIntersectionObserver } from '../../hooks/useIntersectionObserver';
|
||||
import useLang from '../../hooks/useLang';
|
||||
|
||||
import Modal from '../ui/Modal';
|
||||
import Button from '../ui/Button';
|
||||
@ -43,6 +44,7 @@ const StickerSetModal: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const lang = useLang();
|
||||
|
||||
const {
|
||||
observe: observeIntersection,
|
||||
@ -98,7 +100,11 @@ const StickerSetModal: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
color={stickerSet.installedDate ? 'danger' : 'primary'}
|
||||
onClick={handleButtonClick}
|
||||
>
|
||||
{`${stickerSet.installedDate ? 'Remove' : 'Add'} ${stickerSet.count} stickers`}
|
||||
{lang(
|
||||
stickerSet.installedDate ? 'StickerPack.RemoveStickerCount' : 'StickerPack.AddStickerCount',
|
||||
stickerSet.count,
|
||||
'i',
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
|
@ -7,6 +7,7 @@ import buildClassName from '../../util/buildClassName';
|
||||
import trimText from '../../util/trimText';
|
||||
import renderText from './helpers/renderText';
|
||||
import { formatPastTimeShort } from '../../util/dateFormat';
|
||||
import useLang from '../../hooks/useLang';
|
||||
|
||||
import Media from './Media';
|
||||
import Link from '../ui/Link';
|
||||
@ -23,13 +24,15 @@ type OwnProps = {
|
||||
};
|
||||
|
||||
const WebLink: FC<OwnProps> = ({ message, senderTitle, onMessageClick }) => {
|
||||
const lang = useLang();
|
||||
|
||||
let linkData: ApiWebPage | undefined = getMessageWebPage(message);
|
||||
|
||||
if (!linkData) {
|
||||
const link = getFirstLinkInMessage(message);
|
||||
if (link) {
|
||||
const { url, domain } = link;
|
||||
const messageText = getMessageSummaryText(message);
|
||||
const messageText = getMessageSummaryText(lang, message);
|
||||
|
||||
linkData = {
|
||||
siteName: domain.replace(/^www./, ''),
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from '../../../lib/teact/teact';
|
||||
|
||||
import { ApiChat, ApiMessage, ApiUser } from '../../../api/types';
|
||||
import { LangFn } from '../../../hooks/useLang';
|
||||
import {
|
||||
getChatTitle,
|
||||
getMessageContent,
|
||||
@ -25,6 +26,7 @@ interface ActionMessageTextOptions {
|
||||
const NBSP = '\u00A0';
|
||||
|
||||
export function renderActionMessageText(
|
||||
lang: LangFn,
|
||||
message: ApiMessage,
|
||||
actionOrigin?: ApiUser | ApiChat,
|
||||
targetUser?: ApiUser,
|
||||
@ -66,7 +68,7 @@ export function renderActionMessageText(
|
||||
unprocessed,
|
||||
'%message%',
|
||||
targetMessage
|
||||
? renderMessageContent(targetMessage, textOptions)
|
||||
? renderMessageContent(lang, targetMessage, textOptions)
|
||||
: 'a message',
|
||||
);
|
||||
unprocessed = processed.pop() as string;
|
||||
@ -104,8 +106,8 @@ function renderProductContent(message: ApiMessage) {
|
||||
: 'a product';
|
||||
}
|
||||
|
||||
function renderMessageContent(message: ApiMessage, options: ActionMessageTextOptions = {}) {
|
||||
const text = getMessageSummaryText(message);
|
||||
function renderMessageContent(lang: LangFn, message: ApiMessage, options: ActionMessageTextOptions = {}) {
|
||||
const text = getMessageSummaryText(lang, message);
|
||||
const {
|
||||
photo, video, document, sticker,
|
||||
} = getMessageContent(message);
|
||||
|
@ -80,7 +80,7 @@ const NewChatButton: FC<OwnProps> = ({
|
||||
color="primary"
|
||||
className={isMenuOpen ? 'active' : ''}
|
||||
onClick={toggleIsMenuOpen}
|
||||
ariaLabel={isMenuOpen ? 'Close' : 'Create new chat'}
|
||||
ariaLabel={lang(isMenuOpen ? 'Close' : 'NewMessageTitle')}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<i className="icon-new-chat-filled" />
|
||||
@ -95,7 +95,7 @@ const NewChatButton: FC<OwnProps> = ({
|
||||
>
|
||||
<MenuItem icon="channel" onClick={onNewChannel}>{lang('NewChannel')}</MenuItem>
|
||||
<MenuItem icon="group" onClick={onNewGroup}>{lang('NewGroup')}</MenuItem>
|
||||
<MenuItem icon="user" onClick={onNewPrivateChat}>New Private Chat</MenuItem>
|
||||
<MenuItem icon="user" onClick={onNewPrivateChat}>{lang('NewMessageTitle')}</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
|
@ -3,7 +3,7 @@ import React, {
|
||||
} from '../../../lib/teact/teact';
|
||||
import { withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLang, { LangFn } from '../../../hooks/useLang';
|
||||
|
||||
import { GlobalActions, MessageListType } from '../../../global/types';
|
||||
import {
|
||||
@ -205,6 +205,7 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
return (
|
||||
<p className="last-message">
|
||||
{renderText(renderActionMessageText(
|
||||
lang,
|
||||
lastMessage,
|
||||
actionOrigin,
|
||||
actionTargetUser,
|
||||
@ -223,7 +224,7 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
{senderName && (
|
||||
<span className="sender-name">{renderText(senderName)}</span>
|
||||
)}
|
||||
{renderMessageSummary(lastMessage!, mediaBlobUrl || mediaThumbnail)}
|
||||
{renderMessageSummary(lang, lastMessage!, mediaBlobUrl || mediaThumbnail)}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@ -273,16 +274,16 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
function renderMessageSummary(message: ApiMessage, blobUrl?: string) {
|
||||
function renderMessageSummary(lang: LangFn, message: ApiMessage, blobUrl?: string) {
|
||||
if (!blobUrl) {
|
||||
return renderText(getMessageSummaryText(message));
|
||||
return renderText(getMessageSummaryText(lang, message));
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="media-preview">
|
||||
<img src={blobUrl} alt="" />
|
||||
{getMessageVideo(message) && <i className="icon-play" />}
|
||||
{renderText(getMessageSummaryText(message, true))}
|
||||
{renderText(getMessageSummaryText(lang, message, true))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@ -43,6 +43,8 @@ const ChatFolders: FC<StateProps & DispatchProps> = ({
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const transitionRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
@ -84,13 +86,13 @@ const ChatFolders: FC<StateProps & DispatchProps> = ({
|
||||
}
|
||||
|
||||
return [
|
||||
{ title: 'All' },
|
||||
{ title: lang('FilterAllChats') },
|
||||
...displayedFolders.map((folder) => ({
|
||||
title: folder.title,
|
||||
...(folderCountersById && folderCountersById[folder.id]),
|
||||
})),
|
||||
];
|
||||
}, [displayedFolders, folderCountersById]);
|
||||
}, [displayedFolders, folderCountersById, lang]);
|
||||
|
||||
const handleSwitchTab = useCallback((index: number) => {
|
||||
setActiveTab(index);
|
||||
@ -135,8 +137,6 @@ const ChatFolders: FC<StateProps & DispatchProps> = ({
|
||||
shouldRender: shouldRenderPlaceholder, transitionClassNames,
|
||||
} = useShowTransition(!orderedFolderIds, undefined, true);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
function renderCurrentTab() {
|
||||
const activeFolder = Object.values(chatFoldersById)
|
||||
.find(({ title }) => title === folderTabs![activeTab].title);
|
||||
|
@ -76,6 +76,7 @@ const LeftMainHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
setGlobalSearchDate,
|
||||
setSettingOption,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
const hasMenu = content === LeftColumnContent.ChatList;
|
||||
const clearedDateSearchParam = { date: undefined };
|
||||
const clearedChatSearchParam = { id: undefined };
|
||||
@ -107,12 +108,12 @@ const LeftMainHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
color="translucent"
|
||||
className={isOpen ? 'active' : ''}
|
||||
onClick={hasMenu ? onTrigger : () => onReset()}
|
||||
ariaLabel={hasMenu ? 'Open menu' : 'Return to chat list'}
|
||||
ariaLabel={hasMenu ? lang('AccDescrOpenMenu2') : 'Return to chat list'}
|
||||
>
|
||||
<div className={buildClassName('animated-menu-icon', !hasMenu && 'state-back')} />
|
||||
</Button>
|
||||
);
|
||||
}, [hasMenu, onReset]);
|
||||
}, [hasMenu, lang, onReset]);
|
||||
|
||||
const handleSearchFocus = useCallback(() => {
|
||||
if (!searchQuery) {
|
||||
@ -148,8 +149,6 @@ const LeftMainHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
setSettingOption({ animationLevel: newLevel });
|
||||
}, [animationLevel, setSettingOption]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const isSearchFocused = (
|
||||
Boolean(globalSearchChatId)
|
||||
|| content === LeftColumnContent.GlobalSearch
|
||||
@ -198,10 +197,10 @@ const LeftMainHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
icon="darkmode"
|
||||
onClick={handleDarkModeToggle}
|
||||
>
|
||||
<span className="menu-item-name">Dark Mode</span>
|
||||
<span className="menu-item-name">{lang('lng_menu_night_mode')}</span>
|
||||
<Switcher
|
||||
id="darkmode"
|
||||
label="Toggle Dark Mode"
|
||||
label={lang(theme === 'dark' ? 'lng_settings_disable_night_theme' : 'lng_settings_enable_night_theme')}
|
||||
checked={theme === 'dark'}
|
||||
noAnimation
|
||||
/>
|
||||
@ -221,7 +220,7 @@ const LeftMainHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
icon="help"
|
||||
onClick={openTipsChat}
|
||||
>
|
||||
Telegram Features
|
||||
{lang('TelegramFeatures')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="bug"
|
||||
|
@ -14,6 +14,7 @@ import { formatMonthAndYear, toYearMonth } from '../../../util/dateFormat';
|
||||
import { getSenderName } from './helpers/getSenderName';
|
||||
import { throttle } from '../../../util/schedulers';
|
||||
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import Audio from '../../common/Audio';
|
||||
@ -43,6 +44,7 @@ const AudioResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
focusMessage,
|
||||
openAudioPlayer,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
const currentType = isVoice ? 'voice' : 'audio';
|
||||
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
|
||||
if (lastSyncTime && direction === LoadMoreDirection.Backwards) {
|
||||
@ -115,7 +117,12 @@ const AudioResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
noFastList
|
||||
>
|
||||
{!canRenderContents && <Loading />}
|
||||
{canRenderContents && (!foundIds || foundIds.length === 0) && <NothingFound />}
|
||||
{canRenderContents && (!foundIds || foundIds.length === 0) && (
|
||||
<NothingFound
|
||||
text={lang('ChatList.Search.NoResults')}
|
||||
description={lang('ChatList.Search.NoResultsDescription')}
|
||||
/>
|
||||
)}
|
||||
{canRenderContents && foundIds && foundIds.length > 0 && renderList()}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
|
@ -22,7 +22,7 @@ import renderText from '../../common/helpers/renderText';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import useMedia from '../../../hooks/useMedia';
|
||||
import { formatPastTimeShort } from '../../../util/dateFormat';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLang, { LangFn } from '../../../hooks/useLang';
|
||||
|
||||
import Avatar from '../../common/Avatar';
|
||||
import VerifiedIcon from '../../common/VerifiedIcon';
|
||||
@ -62,7 +62,7 @@ const ChatMessage: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
focusMessage({ chatId, messageId: message.id });
|
||||
}, [chatId, focusMessage, message.id]);
|
||||
|
||||
useLang();
|
||||
const lang = useLang();
|
||||
|
||||
if (!chat) {
|
||||
return undefined;
|
||||
@ -96,7 +96,7 @@ const ChatMessage: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
</div>
|
||||
<div className="subtitle">
|
||||
<div className="message">
|
||||
{renderMessageSummary(message, mediaBlobUrl || mediaThumbnail, searchQuery)}
|
||||
{renderMessageSummary(lang, message, mediaBlobUrl || mediaThumbnail, searchQuery)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -104,16 +104,16 @@ const ChatMessage: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
function renderMessageSummary(message: ApiMessage, blobUrl?: string, searchQuery?: string) {
|
||||
function renderMessageSummary(lang: LangFn, message: ApiMessage, blobUrl?: string, searchQuery?: string) {
|
||||
if (!blobUrl) {
|
||||
return renderText(getMessageSummaryText(message));
|
||||
return renderText(getMessageSummaryText(lang, message));
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="media-preview">
|
||||
<img src={blobUrl} alt="" />
|
||||
{getMessageVideo(message) && <i className="icon-play" />}
|
||||
{renderText(getMessageSummaryText(message, true), ['emoji', 'highlight'], { highlight: searchQuery })}
|
||||
{renderText(getMessageSummaryText(lang, message, true), ['emoji', 'highlight'], { highlight: searchQuery })}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import { pick } from '../../../util/iteratees';
|
||||
import { getMessageSummaryText } from '../../../modules/helpers';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
import { throttle } from '../../../util/schedulers';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import ChatMessage from './ChatMessage';
|
||||
@ -49,6 +50,7 @@ const ChatMessageResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
searchMessagesGlobal,
|
||||
onSearchDateSelect,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
|
||||
if (lastSyncTime && direction === LoadMoreDirection.Backwards) {
|
||||
runThrottled(() => {
|
||||
@ -79,7 +81,7 @@ const ChatMessageResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
}, [foundIds, globalMessagesByChatId]);
|
||||
|
||||
function renderFoundMessage(message: ApiMessage) {
|
||||
const text = getMessageSummaryText(message);
|
||||
const text = getMessageSummaryText(lang, message);
|
||||
const chat = chatsById[message.chatId];
|
||||
|
||||
if (!text || !chat) {
|
||||
@ -113,7 +115,12 @@ const ChatMessageResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{nothingFound && <NothingFound />}
|
||||
{nothingFound && (
|
||||
<NothingFound
|
||||
text={lang('ChatList.Search.NoResults')}
|
||||
description={lang('ChatList.Search.NoResultsDescription')}
|
||||
/>
|
||||
)}
|
||||
{!!foundMessages.length && foundMessages.map(renderFoundMessage)}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
|
@ -159,7 +159,7 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
const lang = useLang();
|
||||
|
||||
function renderFoundMessage(message: ApiMessage) {
|
||||
const text = getMessageSummaryText(message);
|
||||
const text = getMessageSummaryText(lang, message);
|
||||
const chat = chatsById[message.chatId];
|
||||
|
||||
if (!text || !chat) {
|
||||
@ -199,7 +199,12 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{nothingFound && <NothingFound />}
|
||||
{nothingFound && (
|
||||
<NothingFound
|
||||
text={lang('ChatList.Search.NoResults')}
|
||||
description={lang('ChatList.Search.NoResultsDescription')}
|
||||
/>
|
||||
)}
|
||||
{!!localResults.length && (
|
||||
<div className="chat-selection no-selection no-scrollbar">
|
||||
{localResults.map((id) => (
|
||||
@ -215,9 +220,11 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
<div className="search-section">
|
||||
<h3 className="section-heading">
|
||||
{localResults.length > LESS_LIST_ITEMS_AMOUNT && (
|
||||
<Link onClick={handleClickShowMoreLocal}>{shouldShowMoreLocal ? 'Show less' : 'Show more'}</Link>
|
||||
<Link onClick={handleClickShowMoreLocal}>
|
||||
{lang(shouldShowMoreLocal ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')}
|
||||
</Link>
|
||||
)}
|
||||
Contacts and Chats
|
||||
{lang('DialogList.SearchSectionDialogs')}
|
||||
</h3>
|
||||
{localResults.map((id, index) => {
|
||||
if (!shouldShowMoreLocal && index >= LESS_LIST_ITEMS_AMOUNT) {
|
||||
@ -237,9 +244,11 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
<div className="search-section">
|
||||
<h3 className="section-heading">
|
||||
{globalResults.length > LESS_LIST_ITEMS_AMOUNT && (
|
||||
<Link onClick={handleClickShowMoreGlobal}>{shouldShowMoreGlobal ? 'Show less' : 'Show more'}</Link>
|
||||
<Link onClick={handleClickShowMoreGlobal}>
|
||||
{lang(shouldShowMoreGlobal ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')}
|
||||
</Link>
|
||||
)}
|
||||
Global Search
|
||||
{lang('DialogList.SearchSectionGlobal')}
|
||||
</h3>
|
||||
{globalResults.map((id, index) => {
|
||||
if (!shouldShowMoreGlobal && index >= LESS_LIST_ITEMS_AMOUNT) {
|
||||
|
@ -16,6 +16,7 @@ import { getSenderName } from './helpers/getSenderName';
|
||||
import { throttle } from '../../../util/schedulers';
|
||||
import { getMessageDocument } from '../../../modules/helpers';
|
||||
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
import Document from '../../common/Document';
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
@ -43,6 +44,7 @@ const FileResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
searchMessagesGlobal,
|
||||
focusMessage,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
|
||||
if (lastSyncTime && direction === LoadMoreDirection.Backwards) {
|
||||
runThrottled(() => {
|
||||
@ -109,7 +111,12 @@ const FileResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
noFastList
|
||||
>
|
||||
{!canRenderContents && <Loading />}
|
||||
{canRenderContents && (!foundIds || foundIds.length === 0) && <NothingFound />}
|
||||
{canRenderContents && (!foundIds || foundIds.length === 0) && (
|
||||
<NothingFound
|
||||
text={lang('ChatList.Search.NoResults')}
|
||||
description={lang('ChatList.Search.NoResultsDescription')}
|
||||
/>
|
||||
)}
|
||||
{canRenderContents && foundIds && foundIds.length > 0 && renderList()}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
|
@ -14,6 +14,7 @@ import { formatMonthAndYear, toYearMonth } from '../../../util/dateFormat';
|
||||
import { getSenderName } from './helpers/getSenderName';
|
||||
import { throttle } from '../../../util/schedulers';
|
||||
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import WebLink from '../../common/WebLink';
|
||||
@ -41,6 +42,7 @@ const LinkResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
searchMessagesGlobal,
|
||||
focusMessage,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
|
||||
if (lastSyncTime && direction === LoadMoreDirection.Backwards) {
|
||||
runThrottled(() => {
|
||||
@ -103,7 +105,12 @@ const LinkResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
noFastList
|
||||
>
|
||||
{!canRenderContents && <Loading />}
|
||||
{canRenderContents && (!foundIds || foundIds.length === 0) && <NothingFound />}
|
||||
{canRenderContents && (!foundIds || foundIds.length === 0) && (
|
||||
<NothingFound
|
||||
text={lang('ChatList.Search.NoResults')}
|
||||
description={lang('ChatList.Search.NoResultsDescription')}
|
||||
/>
|
||||
)}
|
||||
{canRenderContents && foundIds && foundIds.length > 0 && renderList()}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
|
@ -7,17 +7,18 @@ import { GlobalActions } from '../../../global/types';
|
||||
import { LoadMoreDirection, MediaViewerOrigin } from '../../../types';
|
||||
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
import { SLIDE_TRANSITION_DURATION } from '../../../config';
|
||||
import { createMapStateToProps, StateProps } from './helpers/createMapStateToProps';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { throttle } from '../../../util/schedulers';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import Media from '../../common/Media';
|
||||
import ChatMessage from './ChatMessage';
|
||||
import NothingFound from '../../common/NothingFound';
|
||||
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||
import { SLIDE_TRANSITION_DURATION } from '../../../config';
|
||||
import Loading from '../../ui/Loading';
|
||||
|
||||
export type OwnProps = {
|
||||
@ -39,6 +40,7 @@ const MediaResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
searchMessagesGlobal,
|
||||
openMediaViewer,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
|
||||
if (lastSyncTime && direction === LoadMoreDirection.Backwards) {
|
||||
runThrottled(() => {
|
||||
@ -115,7 +117,12 @@ const MediaResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
noFastList
|
||||
>
|
||||
{!canRenderContents && <Loading />}
|
||||
{canRenderContents && (!foundIds || foundIds.length === 0) && <NothingFound />}
|
||||
{canRenderContents && (!foundIds || foundIds.length === 0) && (
|
||||
<NothingFound
|
||||
text={lang('ChatList.Search.NoResults')}
|
||||
description={lang('ChatList.Search.NoResultsDescription')}
|
||||
/>
|
||||
)}
|
||||
{isMediaGrid && renderGallery()}
|
||||
{isMessageList && renderSearchResult()}
|
||||
</InfiniteScroll>
|
||||
|
@ -199,7 +199,7 @@ const SettingsEditProfile: FC<StateProps & DispatchProps> = ({
|
||||
/>
|
||||
|
||||
<p className="settings-item-description">
|
||||
{renderText(lang('BioAbout'), ['br', 'simple_markdown'])}
|
||||
{renderText(lang('lng_settings_about_bio'), ['br', 'simple_markdown'])}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -219,7 +219,7 @@ const SettingsEditProfile: FC<StateProps & DispatchProps> = ({
|
||||
</p>
|
||||
{username && (
|
||||
<p className="settings-item-description">
|
||||
This link opens a chat with you:<br />
|
||||
{lang('lng_username_link')}<br />
|
||||
<span className="username-link">https://t.me/{username}</span>
|
||||
</p>
|
||||
)}
|
||||
|
@ -31,11 +31,6 @@ type StateProps = ISettings['byKey'] & {
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, 'setSettingOption' | 'loadStickerSets' | 'loadAddedStickers'>;
|
||||
|
||||
const KEYBOARD_SEND_OPTIONS = !IS_TOUCH_ENV ? [
|
||||
{ value: 'enter', label: 'Send by Enter', subLabel: 'New line by Shift + Enter' },
|
||||
{ value: 'ctrl-enter', label: `Send by ${IS_MAC_OS ? 'Cmd' : 'Ctrl'} + Enter`, subLabel: 'New line by Enter' },
|
||||
] : undefined;
|
||||
|
||||
const ANIMATION_LEVEL_OPTIONS = [
|
||||
'Solid and Steady',
|
||||
'Nice and Fast',
|
||||
@ -67,6 +62,17 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
const [isModalOpen, openModal, closeModal] = useFlag();
|
||||
const [sticker, setSticker] = useState<ApiSticker>();
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const KEYBOARD_SEND_OPTIONS = !IS_TOUCH_ENV ? [
|
||||
{ value: 'enter', label: lang('lng_settings_send_enter'), subLabel: 'New line by Shift + Enter' },
|
||||
{
|
||||
value: 'ctrl-enter',
|
||||
label: lang(IS_MAC_OS ? 'lng_settings_send_cmdenter' : 'lng_settings_send_ctrlenter'),
|
||||
subLabel: 'New line by Enter',
|
||||
},
|
||||
] : undefined;
|
||||
|
||||
useEffect(() => {
|
||||
loadStickerSets();
|
||||
}, [loadStickerSets]);
|
||||
@ -96,9 +102,6 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
openModal();
|
||||
}, [openModal]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
|
||||
const stickerSets = stickerSetIds && stickerSetIds.map((id: string) => {
|
||||
return stickerSetsById && stickerSetsById[id] && stickerSetsById[id].installedDate ? stickerSetsById[id] : false;
|
||||
}).filter(Boolean);
|
||||
|
@ -83,7 +83,7 @@ const SettingsHeader: FC<OwnProps & DispatchProps> = ({
|
||||
function renderHeaderContent() {
|
||||
switch (currentScreen) {
|
||||
case SettingsScreens.EditProfile:
|
||||
return <h3>{lang('EditProfile')}</h3>;
|
||||
return <h3>{lang('lng_settings_information')}</h3>;
|
||||
case SettingsScreens.General:
|
||||
return <h3>{lang('General')}</h3>;
|
||||
case SettingsScreens.Notifications:
|
||||
|
@ -40,7 +40,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
icon="edit"
|
||||
onClick={() => onScreenSelect(SettingsScreens.EditProfile)}
|
||||
>
|
||||
{lang('EditProfile')}
|
||||
{lang('lng_settings_information')}
|
||||
</ListItem>
|
||||
<ListItem
|
||||
icon="folder"
|
||||
|
@ -41,7 +41,7 @@ const SenderInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
focusMessage({ chatId, messageId });
|
||||
}, [chatId, focusMessage, messageId, closeMediaViewer]);
|
||||
|
||||
useLang();
|
||||
const lang = useLang();
|
||||
|
||||
if (!sender || (!message && !isAvatar)) {
|
||||
return undefined;
|
||||
@ -62,7 +62,7 @@ const SenderInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
{senderTitle && renderText(senderTitle)}
|
||||
</div>
|
||||
<div className="date">
|
||||
{isAvatar ? 'Profile photo' : formatMediaDateTime(message!.date * 1000)}
|
||||
{isAvatar ? lang('lng_mediaview_profile_photo') : formatMediaDateTime(message!.date * 1000)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -65,7 +65,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
useEnsureMessage(message.chatId, message.replyToMessageId, targetMessage);
|
||||
useFocusMessage(ref, message.chatId, isFocused, focusDirection, noFocusHighlight);
|
||||
|
||||
useLang();
|
||||
const lang = useLang();
|
||||
|
||||
const noAppearanceAnimation = appearanceOrder <= 0;
|
||||
const [isShown, markShown] = useFlag(noAppearanceAnimation);
|
||||
@ -79,6 +79,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
const { transitionClassNames } = useShowTransition(isShown, undefined, noAppearanceAnimation, false);
|
||||
|
||||
const content = renderActionMessageText(
|
||||
lang,
|
||||
message,
|
||||
sender,
|
||||
targetUser,
|
||||
|
@ -165,7 +165,9 @@ const HeaderMenuContainer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
icon="delete"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
{lang(isPrivate ? 'Delete' : (canDeleteChat ? 'Delete and Leave' : 'Leave'))}
|
||||
{lang(isPrivate
|
||||
? 'Delete'
|
||||
: (canDeleteChat ? 'GroupInfo.DeleteAndExit' : (isChannel ? 'LeaveChannel' : 'Group.LeaveGroup')))}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Menu>
|
||||
|
@ -31,10 +31,11 @@ type OwnProps = {
|
||||
const HeaderPinnedMessage: FC<OwnProps> = ({
|
||||
message, count, index, customTitle, className, onUnpinMessage, onClick, onAllPinnedClick,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
const mediaThumbnail = useWebpThumbnail(message);
|
||||
const mediaBlobUrl = useMedia(getMessageMediaHash(message, 'pictogram'));
|
||||
|
||||
const text = getMessageSummaryText(message, Boolean(mediaThumbnail));
|
||||
const text = getMessageSummaryText(lang, message, Boolean(mediaThumbnail));
|
||||
const [isUnpinDialogOpen, openUnpinDialog, closeUnpinDialog] = useFlag();
|
||||
|
||||
const handleUnpinMessage = useCallback(() => {
|
||||
@ -45,8 +46,6 @@ const HeaderPinnedMessage: FC<OwnProps> = ({
|
||||
}
|
||||
}, [closeUnpinDialog, onUnpinMessage, message.id]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
return (
|
||||
<div className={buildClassName('HeaderPinnedMessage-wrapper', className)}>
|
||||
{count > 1 && (
|
||||
|
@ -8,9 +8,10 @@ import { GlobalActions } from '../../../global/types';
|
||||
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import { isChatPrivate } from '../../../modules/helpers';
|
||||
import { formatInteger, formatIntegerCompact } from '../../../util/textFormat';
|
||||
import { formatIntegerCompact } from '../../../util/textFormat';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { selectThreadInfo } from '../../../modules/selectors';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
|
||||
import Avatar from '../../common/Avatar';
|
||||
|
||||
@ -32,6 +33,7 @@ type DispatchProps = Pick<GlobalActions, 'openChat'>;
|
||||
const CommentButton: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
disabled, threadInfo, usersById, chatsById, openChat,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
const {
|
||||
threadId, chatId, messagesCount, lastMessageId, lastReadInboxMessageId, recentReplierIds,
|
||||
} = threadInfo;
|
||||
@ -76,24 +78,14 @@ const CommentButton: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
<i className="icon-comments-sticker" />
|
||||
{(!recentRepliers || recentRepliers.length === 0) && <i className="icon-comments" />}
|
||||
{renderRecentRepliers()}
|
||||
<div className="label">{renderLabel(messagesCount)}</div>
|
||||
<div className="label">
|
||||
{messagesCount ? lang('Comments', messagesCount, 'i') : lang('LeaveAComment')}
|
||||
</div>
|
||||
<i className="icon-next" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function renderLabel(messagesCount: number) {
|
||||
if (messagesCount === 0) {
|
||||
return 'Leave a Comment';
|
||||
}
|
||||
|
||||
if (messagesCount === 1) {
|
||||
return '1 Comment';
|
||||
}
|
||||
|
||||
return `${formatInteger(messagesCount)} Comments`;
|
||||
}
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { message }) => {
|
||||
const { threadId, chatId } = message.threadInfo!;
|
||||
|
@ -756,7 +756,7 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
color="translucent-white"
|
||||
round
|
||||
size="tiny"
|
||||
ariaLabel="Forward message"
|
||||
ariaLabel={lang('lng_context_forward_msg')}
|
||||
onClick={isLastInDocumentGroup ? handleGroupForward : handleForward}
|
||||
>
|
||||
<i className="icon-share-filled" />
|
||||
|
@ -21,7 +21,7 @@ export function getMessageCopyOptions(
|
||||
|
||||
if (canImageBeCopied) {
|
||||
options.push({
|
||||
label: 'Copy Media',
|
||||
label: 'lng_context_copy_image',
|
||||
handler: () => {
|
||||
mediaLoader.fetch(mediaHash, ApiMediaFormat.BlobUrl).then(copyImageToClipboard);
|
||||
|
||||
@ -57,7 +57,7 @@ export function getMessageCopyOptions(
|
||||
|
||||
if (onCopyLink) {
|
||||
options.push({
|
||||
label: 'CopyMessageLink',
|
||||
label: 'lng_context_copy_message_link',
|
||||
handler: () => {
|
||||
onCopyLink();
|
||||
|
||||
@ -73,11 +73,11 @@ export function getMessageCopyOptions(
|
||||
|
||||
function getCopyLabel(hasSelection: boolean, canImageBeCopied: boolean): string {
|
||||
if (hasSelection) {
|
||||
return 'Copy Selected Text';
|
||||
return 'lng_context_copy_selected';
|
||||
}
|
||||
|
||||
if (canImageBeCopied) {
|
||||
return 'Copy Text';
|
||||
return 'lng_context_copy_text';
|
||||
}
|
||||
|
||||
return 'Copy';
|
||||
|
@ -37,6 +37,7 @@ import useProfileViewportIds from './hooks/useProfileViewportIds';
|
||||
import useProfileState from './hooks/useProfileState';
|
||||
import useTransitionFixes from './hooks/useTransitionFixes';
|
||||
import useAsyncRendering from './hooks/useAsyncRendering';
|
||||
import useLang from '../../hooks/useLang';
|
||||
|
||||
import Transition from '../ui/Transition';
|
||||
import InfiniteScroll from '../ui/InfiniteScroll';
|
||||
@ -120,6 +121,8 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const transitionRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
|
||||
const tabs = useMemo(() => ([
|
||||
@ -228,16 +231,16 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
text = areMembersHidden ? 'You have no access to group members list.' : 'No members found';
|
||||
break;
|
||||
case 'documents':
|
||||
text = 'No documents found.';
|
||||
text = lang('lng_media_file_empty_search');
|
||||
break;
|
||||
case 'links':
|
||||
text = 'No links found.';
|
||||
text = lang('lng_media_link_empty_search');
|
||||
break;
|
||||
case 'audio':
|
||||
text = 'No audio found.';
|
||||
text = lang('lng_media_song_empty_search');
|
||||
break;
|
||||
default:
|
||||
text = 'No media found.';
|
||||
text = lang('SharedMedia.EmptyTitle');
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -235,7 +235,7 @@ const RightHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
return (
|
||||
<SearchInput
|
||||
value={stickerSearchQuery}
|
||||
placeholder="Search Stickers"
|
||||
placeholder={lang('SearchStickersHint')}
|
||||
onChange={handleStickerSearchQueryChange}
|
||||
/>
|
||||
);
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
isChatChannel,
|
||||
} from '../../modules/helpers';
|
||||
import renderText from '../common/helpers/renderText';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import { orderBy, pick } from '../../util/iteratees';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||
|
||||
@ -60,6 +61,8 @@ const RightSearch: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
searchTextMessagesLocal,
|
||||
focusMessage,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
const foundResults = useMemo(() => {
|
||||
if (!query || !foundIds || !foundIds.length || !messagesById) {
|
||||
return MEMO_EMPTY_ARRAY;
|
||||
@ -98,7 +101,7 @@ const RightSearch: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
message, senderUser, senderChat, onClick,
|
||||
}: Result) => {
|
||||
const title = senderChat ? getChatTitle(senderChat) : getUserFullName(senderUser);
|
||||
const text = getMessageSummaryText(message);
|
||||
const text = getMessageSummaryText(lang, message);
|
||||
|
||||
return (
|
||||
<ListItem className="chat-item-clickable search-result-message m-0" onClick={onClick}>
|
||||
|
@ -12,6 +12,7 @@ import { pick } from '../../util/iteratees';
|
||||
import { selectShouldLoopStickers, selectStickerSet } from '../../modules/selectors';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import useOnChange from '../../hooks/useOnChange';
|
||||
import useLang from '../../hooks/useLang';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
import StickerButton from '../common/StickerButton';
|
||||
@ -37,6 +38,7 @@ const STICKERS_TO_DISPLAY = 5;
|
||||
const StickerSetResult: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
stickerSetId, observeIntersection, set, shouldPlay, loadStickers, toggleStickerSet, isSomeModalOpen, onModalToggle,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
const isAdded = set && Boolean(set.installedDate);
|
||||
const areStickersLoaded = Boolean(set && set.stickers);
|
||||
|
||||
@ -79,7 +81,7 @@ const StickerSetResult: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
<div className="sticker-set-header">
|
||||
<div className="title-wrapper">
|
||||
<h3 className="title">{set.title}</h3>
|
||||
<p className="count">{set.count} stickers</p>
|
||||
<p className="count">{lang('Stickers', set.count, 'i')}</p>
|
||||
</div>
|
||||
<Button
|
||||
className={isAdded ? 'is-added' : undefined}
|
||||
@ -89,7 +91,7 @@ const StickerSetResult: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
fluid
|
||||
onClick={handleAddClick}
|
||||
>
|
||||
{isAdded ? 'Added' : 'Add'}
|
||||
{lang(isAdded ? 'Stickers.Installed' : 'Stickers.Install')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="sticker-set-main">
|
||||
|
@ -111,7 +111,7 @@ const ManageGroupRecentActions: FC<OwnProps & StateProps> = ({ chat }) => {
|
||||
</div>
|
||||
|
||||
<div className="section not-implemented">
|
||||
<h3 className="section-heading">Admins</h3>
|
||||
<h3 className="section-heading">{lang('Channel.Management.Title')}</h3>
|
||||
|
||||
<div className="ListItem no-selection">
|
||||
<Checkbox
|
||||
|
@ -124,7 +124,7 @@ export const MAX_MEDIA_FILES_FOR_ALBUM = 10;
|
||||
export const MAX_ACTIVE_PINNED_CHATS = 5;
|
||||
export const SCHEDULED_WHEN_ONLINE = 0x7FFFFFFE;
|
||||
export const DEFAULT_LANG_PACK = 'android';
|
||||
export const LANG_PACKS = ['android', 'ios'];
|
||||
export const LANG_PACKS = ['android', 'ios', 'tdesktop'];
|
||||
export const TIPS_USERNAME = 'TelegramTips';
|
||||
export const FEEDBACK_URL = 'https://bugs.telegram.org/?tag_ids=41&sort=time';
|
||||
export const DARK_THEME_BG_COLOR = '#0F0F0F';
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { useMemo } from '../lib/teact/teact';
|
||||
import { getDispatch } from '../lib/teact/teactn';
|
||||
import { ApiChat, ApiUser } from '../api/types';
|
||||
import { isChatArchived, getCanDeleteChat, isChatPrivate } from '../modules/helpers';
|
||||
import {
|
||||
isChatArchived, getCanDeleteChat, isChatPrivate, isChatChannel,
|
||||
} from '../modules/helpers';
|
||||
import useLang from './useLang';
|
||||
|
||||
export default ({
|
||||
chat,
|
||||
@ -16,6 +19,8 @@ export default ({
|
||||
folderId?: number;
|
||||
isPinned?: boolean;
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
const {
|
||||
toggleChatPinned,
|
||||
updateChatMutedState,
|
||||
@ -31,23 +36,39 @@ export default ({
|
||||
const isChatWithSelf = privateChatUser && privateChatUser.isSelf;
|
||||
|
||||
const actionUnreadMark = chat.unreadCount || chat.hasUnreadMark
|
||||
? { title: 'Mark as Read', icon: 'readchats', handler: () => toggleChatUnread({ id: chat.id }) }
|
||||
: { title: 'Mark as Unread', icon: 'unread', handler: () => toggleChatUnread({ id: chat.id }) };
|
||||
? { title: lang('MarkAsRead'), icon: 'readchats', handler: () => toggleChatUnread({ id: chat.id }) }
|
||||
: { title: lang('MarkAsUnread'), icon: 'unread', handler: () => toggleChatUnread({ id: chat.id }) };
|
||||
|
||||
const actionPin = isPinned
|
||||
? { title: 'Unpin', icon: 'unpin', handler: () => toggleChatPinned({ id: chat.id, folderId }) }
|
||||
: { title: 'Pin', icon: 'pin', handler: () => toggleChatPinned({ id: chat.id, folderId }) };
|
||||
? {
|
||||
title: lang('UnpinFromTop'),
|
||||
icon: 'unpin',
|
||||
handler: () => toggleChatPinned({ id: chat.id, folderId }),
|
||||
}
|
||||
: { title: lang('PinToTop'), icon: 'pin', handler: () => toggleChatPinned({ id: chat.id, folderId }) };
|
||||
|
||||
const actionMute = chat.isMuted
|
||||
? { title: 'Unmute', icon: 'unmute', handler: () => updateChatMutedState({ chatId: chat.id, isMuted: false }) }
|
||||
: { title: 'Mute', icon: 'mute', handler: () => updateChatMutedState({ chatId: chat.id, isMuted: true }) };
|
||||
? {
|
||||
title: lang('ChatList.Unmute'),
|
||||
icon: 'unmute',
|
||||
handler: () => updateChatMutedState({ chatId: chat.id, isMuted: false }),
|
||||
}
|
||||
: {
|
||||
title: lang('ChatList.Mute'),
|
||||
icon: 'mute',
|
||||
handler: () => updateChatMutedState({ chatId: chat.id, isMuted: true }),
|
||||
};
|
||||
|
||||
const actionArchive = isChatArchived(chat)
|
||||
? { title: 'Unarchive', icon: 'unarchive', handler: () => toggleChatArchived({ id: chat.id }) }
|
||||
: { title: 'Archive', icon: 'archive', handler: () => toggleChatArchived({ id: chat.id }) };
|
||||
? { title: lang('Unarchive'), icon: 'unarchive', handler: () => toggleChatArchived({ id: chat.id }) }
|
||||
: { title: lang('Archive'), icon: 'archive', handler: () => toggleChatArchived({ id: chat.id }) };
|
||||
|
||||
const actionDelete = {
|
||||
title: isChatPrivate(chat.id) ? 'Delete' : (getCanDeleteChat(chat) ? 'Delete and Leave' : 'Leave'),
|
||||
title: isChatPrivate(chat.id)
|
||||
? lang('Delete')
|
||||
: lang(getCanDeleteChat(chat)
|
||||
? 'DeleteChat'
|
||||
: (isChatChannel(chat) ? 'LeaveChannel' : 'Group.LeaveGroup')),
|
||||
icon: 'delete',
|
||||
destructive: true,
|
||||
handler: handleDelete,
|
||||
@ -63,7 +84,7 @@ export default ({
|
||||
actionDelete,
|
||||
];
|
||||
}, [
|
||||
chat, privateChatUser, handleDelete, folderId, isPinned,
|
||||
toggleChatPinned, updateChatMutedState, toggleChatArchived, toggleChatUnread,
|
||||
chat, privateChatUser, lang, isPinned, handleDelete, toggleChatUnread, toggleChatPinned, folderId,
|
||||
updateChatMutedState, toggleChatArchived,
|
||||
]);
|
||||
};
|
||||
|
@ -428,7 +428,7 @@ export function getMessageSenderName(chatId: number, sender?: ApiUser) {
|
||||
}
|
||||
|
||||
if (sender.isSelf) {
|
||||
return 'You';
|
||||
return getTranslation('FromYou');
|
||||
}
|
||||
|
||||
return getUserFirstOrLastName(sender);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {
|
||||
ApiChat, ApiMessage, ApiMessageEntityTypes, ApiUser,
|
||||
} from '../../api/types';
|
||||
import { LangFn } from '../../hooks/useLang';
|
||||
|
||||
import { LOCAL_MESSAGE_ID_BASE, SERVICE_NOTIFICATIONS_USER_ID, RE_LINK_TEMPLATE } from '../../config';
|
||||
import parseEmojiOnlyString from '../../components/common/helpers/parseEmojiOnlyString';
|
||||
@ -26,62 +27,34 @@ export function getMessageOriginalId(message: ApiMessage) {
|
||||
return message.previousLocalId || message.id;
|
||||
}
|
||||
|
||||
export function getMessageSummaryText(message: ApiMessage, noEmoji = false) {
|
||||
export function getMessageSummaryText(lang: LangFn, message: ApiMessage, noEmoji = false) {
|
||||
const {
|
||||
text, photo, video, audio, voice, document, sticker, contact, poll, invoice,
|
||||
} = message.content;
|
||||
|
||||
if (message.groupedId) {
|
||||
if (text) {
|
||||
return `${noEmoji ? '' : '🖼 '}${text.text}`;
|
||||
}
|
||||
|
||||
return 'Album';
|
||||
return `${noEmoji ? '' : '🖼 '}${text ? text.text : lang('lng_in_dlg_album')}`;
|
||||
}
|
||||
|
||||
if (photo) {
|
||||
if (text) {
|
||||
return `${noEmoji ? '' : '🖼 '}${text.text}`;
|
||||
}
|
||||
|
||||
return 'Photo';
|
||||
return `${noEmoji ? '' : '🖼 '}${text ? text.text : lang('AttachPhoto')}`;
|
||||
}
|
||||
|
||||
if (video) {
|
||||
if (video.isGif) {
|
||||
if (text) {
|
||||
return `${noEmoji ? '' : 'GIF '}${text.text}`;
|
||||
}
|
||||
|
||||
return 'GIF';
|
||||
} else {
|
||||
if (text) {
|
||||
return `${noEmoji ? '' : '📹 '}${text.text}`;
|
||||
}
|
||||
|
||||
return 'Video';
|
||||
}
|
||||
return `${noEmoji ? '' : '📹 '}${text ? text.text : lang(video.isGif ? 'AttachGif' : 'AttachVideo')}`;
|
||||
}
|
||||
|
||||
if (sticker) {
|
||||
return `${sticker.emoji} Sticker`;
|
||||
return `${sticker.emoji} ${lang('AttachSticker')} `;
|
||||
}
|
||||
|
||||
if (audio) {
|
||||
const caption = [audio.title, audio.performer].filter(Boolean).join(' — ') || (text && text.text);
|
||||
if (caption) {
|
||||
return `🎧 ${caption}`;
|
||||
}
|
||||
|
||||
return 'Audio';
|
||||
return `${noEmoji ? '' : '🎧 '}${caption || lang('AttachMusic')}`;
|
||||
}
|
||||
|
||||
if (voice) {
|
||||
if (text) {
|
||||
return `${noEmoji ? '' : '🎤 '}${text.text}`;
|
||||
}
|
||||
|
||||
return 'Voice Message';
|
||||
return `${noEmoji ? '' : '🎤 '}${text ? text.text : lang('AttachAudio')}`;
|
||||
}
|
||||
|
||||
if (document) {
|
||||
@ -89,11 +62,11 @@ export function getMessageSummaryText(message: ApiMessage, noEmoji = false) {
|
||||
}
|
||||
|
||||
if (contact) {
|
||||
return 'Contact';
|
||||
return lang('AttachContact');
|
||||
}
|
||||
|
||||
if (poll) {
|
||||
return `📊 ${poll.summary.question}`;
|
||||
return `${noEmoji ? '' : '📊 '}${poll.summary.question}`;
|
||||
}
|
||||
|
||||
if (invoice) {
|
||||
@ -107,60 +80,6 @@ export function getMessageSummaryText(message: ApiMessage, noEmoji = false) {
|
||||
return CONTENT_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
export function getNotificationText(message: ApiMessage) {
|
||||
const {
|
||||
text, photo, video, audio, voice, document, sticker, contact, poll, invoice,
|
||||
} = message.content;
|
||||
|
||||
if (message.groupedId) {
|
||||
return `🖼 ${text ? text.text : 'Album'}`;
|
||||
}
|
||||
|
||||
if (photo) {
|
||||
return `🖼 ${text ? text.text : 'Photo'}`;
|
||||
}
|
||||
|
||||
if (video) {
|
||||
return `📹 ${text ? text.text : video.isGif ? 'GIF' : 'Video'}`;
|
||||
}
|
||||
|
||||
if (sticker) {
|
||||
return `${sticker.emoji} Sticker `;
|
||||
}
|
||||
|
||||
if (audio) {
|
||||
const caption = [audio.title, audio.performer].filter(Boolean).join(' — ') || (text && text.text);
|
||||
return `🎧 ${caption || 'Audio'}`;
|
||||
}
|
||||
|
||||
if (voice) {
|
||||
return `🎤 ${text ? text.text : 'Voice Message'}`;
|
||||
}
|
||||
|
||||
if (document) {
|
||||
return `📎 ${text ? text.text : document.fileName}`;
|
||||
}
|
||||
|
||||
if (contact) {
|
||||
return 'Contact';
|
||||
}
|
||||
|
||||
if (poll) {
|
||||
return `📊 ${poll.summary.question}`;
|
||||
}
|
||||
|
||||
if (invoice) {
|
||||
return 'Invoice';
|
||||
}
|
||||
|
||||
if (text) {
|
||||
return text.text;
|
||||
}
|
||||
|
||||
return CONTENT_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
|
||||
export function getMessageText(message: ApiMessage) {
|
||||
const {
|
||||
text, sticker, photo, video, audio, voice, document, poll, webPage, contact, invoice,
|
||||
|
@ -70,7 +70,7 @@ export function getUserStatus(user: ApiUser, lang: LangFn) {
|
||||
}
|
||||
|
||||
if (user.type && user.type === 'userTypeBot') {
|
||||
return 'bot';
|
||||
return lang('Bot');
|
||||
}
|
||||
|
||||
if (!user.status) {
|
||||
|
@ -7,11 +7,12 @@ import {
|
||||
getChatTitle,
|
||||
getMessageAction,
|
||||
getMessageSenderName,
|
||||
getNotificationText,
|
||||
getMessageSummaryText,
|
||||
getPrivateChatUserId,
|
||||
isActionMessage,
|
||||
isChatChannel,
|
||||
} from '../modules/helpers';
|
||||
import { getTranslation } from './langProvider';
|
||||
import { replaceSettings } from '../modules/reducers';
|
||||
import { selectChatMessage, selectUser } from '../modules/selectors';
|
||||
import { IS_SERVICE_WORKER_SUPPORTED } from './environment';
|
||||
@ -225,6 +226,7 @@ function getNotificationContent(chat: ApiChat, message: ApiMessage) {
|
||||
? chat
|
||||
: messageSender;
|
||||
body = renderActionMessageText(
|
||||
getTranslation,
|
||||
message,
|
||||
actionOrigin,
|
||||
actionTargetUser,
|
||||
@ -234,7 +236,7 @@ function getNotificationContent(chat: ApiChat, message: ApiMessage) {
|
||||
) as string;
|
||||
} else {
|
||||
const senderName = getMessageSenderName(chat.id, messageSender);
|
||||
const summary = getNotificationText(message);
|
||||
const summary = getMessageSummaryText(getTranslation, message);
|
||||
|
||||
body = senderName ? `${senderName}: ${summary}` : summary;
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ export function expectCommentButton(
|
||||
expect(button.querySelector('.label')).toHaveTextContent(`${commentsCount} Comments`);
|
||||
expect(button.querySelectorAll('.Avatar')).toHaveLength(Math.min(authorsCount, 3));
|
||||
} else {
|
||||
expect(button.querySelector('.label')).toHaveTextContent('Leave a Comment');
|
||||
expect(button.querySelector('.label')).toHaveTextContent('Leave a comment');
|
||||
expect(button.querySelectorAll('.Avatar')).toHaveLength(0);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user