Settings / General: Support time formats (#1524)
This commit is contained in:
parent
f8640762af
commit
6028e0b4ce
@ -365,8 +365,8 @@ const Audio: FC<OwnProps> = ({
|
|||||||
)}
|
)}
|
||||||
{origin === AudioOrigin.Search && renderWithTitle()}
|
{origin === AudioOrigin.Search && renderWithTitle()}
|
||||||
{origin !== AudioOrigin.Search && audio && renderAudio(
|
{origin !== AudioOrigin.Search && audio && renderAudio(
|
||||||
lang, audio, duration, isPlaying, playProgress, bufferedProgress, seekerRef, (isDownloading || isUploading),
|
lang, audio, duration, isPlaying, playProgress, bufferedProgress, seekerRef,
|
||||||
date, transferProgress, onDateClick ? handleDateClick : undefined,
|
(isDownloading || isUploading), date, transferProgress, onDateClick ? handleDateClick : undefined,
|
||||||
)}
|
)}
|
||||||
{origin === AudioOrigin.SharedMedia && (voice || video) && renderWithTitle()}
|
{origin === AudioOrigin.SharedMedia && (voice || video) && renderWithTitle()}
|
||||||
{origin === AudioOrigin.Inline && voice && renderVoice(voice, renderedWaveform, playProgress, isMediaUnread)}
|
{origin === AudioOrigin.Inline && voice && renderVoice(voice, renderedWaveform, playProgress, isMediaUnread)}
|
||||||
@ -417,7 +417,9 @@ function renderAudio(
|
|||||||
{date && (
|
{date && (
|
||||||
<>
|
<>
|
||||||
<span className="bullet">•</span>
|
<span className="bullet">•</span>
|
||||||
<Link className="date" onClick={handleDateClick}>{formatMediaDateTime(lang, date * 1000, true)}</Link>
|
<Link className="date" onClick={handleDateClick}>
|
||||||
|
{formatMediaDateTime(lang, date * 1000, true)}
|
||||||
|
</Link>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -348,10 +348,10 @@ function formatSubmitLabel(lang: LangFn, date: Date) {
|
|||||||
const today = formatDateToString(new Date(), lang.code);
|
const today = formatDateToString(new Date(), lang.code);
|
||||||
|
|
||||||
if (day === today) {
|
if (day === today) {
|
||||||
return lang('Conversation.ScheduleMessage.SendToday', formatTime(date));
|
return lang('Conversation.ScheduleMessage.SendToday', formatTime(date, lang));
|
||||||
}
|
}
|
||||||
|
|
||||||
return lang('Conversation.ScheduleMessage.SendOn', [day, formatTime(date)]);
|
return lang('Conversation.ScheduleMessage.SendOn', [day, formatTime(date, lang)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(CalendarModal);
|
export default memo(CalendarModal);
|
||||||
|
|||||||
@ -55,9 +55,9 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
areMessagesLoaded,
|
areMessagesLoaded,
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
|
serverTimeOffset,
|
||||||
loadFullUser,
|
loadFullUser,
|
||||||
openMediaViewer,
|
openMediaViewer,
|
||||||
serverTimeOffset,
|
|
||||||
}) => {
|
}) => {
|
||||||
const { id: userId } = user || {};
|
const { id: userId } = user || {};
|
||||||
const fullName = getUserFullName(user);
|
const fullName = getUserFullName(user);
|
||||||
|
|||||||
@ -47,9 +47,9 @@ const ProfileInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
connectionState,
|
connectionState,
|
||||||
animationLevel,
|
animationLevel,
|
||||||
|
serverTimeOffset,
|
||||||
loadFullUser,
|
loadFullUser,
|
||||||
openMediaViewer,
|
openMediaViewer,
|
||||||
serverTimeOffset,
|
|
||||||
}) => {
|
}) => {
|
||||||
const { id: userId } = user || {};
|
const { id: userId } = user || {};
|
||||||
const { id: chatId } = chat || {};
|
const { id: chatId } = chat || {};
|
||||||
@ -232,7 +232,12 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { animationLevel } = global.settings.byKey;
|
const { animationLevel } = global.settings.byKey;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
connectionState, user, chat, isSavedMessages, animationLevel, serverTimeOffset,
|
connectionState,
|
||||||
|
user,
|
||||||
|
chat,
|
||||||
|
isSavedMessages,
|
||||||
|
animationLevel,
|
||||||
|
serverTimeOffset,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, ['loadFullUser', 'openMediaViewer']),
|
(setGlobal, actions): DispatchProps => pick(actions, ['loadFullUser', 'openMediaViewer']),
|
||||||
|
|||||||
@ -23,7 +23,9 @@ type OwnProps = {
|
|||||||
onMessageClick: (messageId: number, chatId: number) => void;
|
onMessageClick: (messageId: number, chatId: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const WebLink: FC<OwnProps> = ({ message, senderTitle, onMessageClick }) => {
|
const WebLink: FC<OwnProps> = ({
|
||||||
|
message, senderTitle, onMessageClick,
|
||||||
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
let linkData: ApiWebPage | undefined = getMessageWebPage(message);
|
let linkData: ApiWebPage | undefined = getMessageWebPage(message);
|
||||||
|
|||||||
@ -292,7 +292,10 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
{chat.isVerified && <VerifiedIcon />}
|
{chat.isVerified && <VerifiedIcon />}
|
||||||
{isMuted && <i className="icon-muted-chat" />}
|
{isMuted && <i className="icon-muted-chat" />}
|
||||||
{chat.lastMessage && (
|
{chat.lastMessage && (
|
||||||
<LastMessageMeta message={chat.lastMessage} outgoingStatus={lastMessageOutgoingStatus} />
|
<LastMessageMeta
|
||||||
|
message={chat.lastMessage}
|
||||||
|
outgoingStatus={lastMessageOutgoingStatus}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="subtitle">
|
<div className="subtitle">
|
||||||
|
|||||||
@ -4,11 +4,12 @@ import React, {
|
|||||||
import { withGlobal } from '../../../lib/teact/teactn';
|
import { withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { GlobalActions } from '../../../global/types';
|
import { GlobalActions } from '../../../global/types';
|
||||||
import { SettingsScreens, ISettings } from '../../../types';
|
import { SettingsScreens, ISettings, TimeFormat } from '../../../types';
|
||||||
import { ApiSticker, ApiStickerSet } from '../../../api/types';
|
import { ApiSticker, ApiStickerSet } from '../../../api/types';
|
||||||
|
|
||||||
import { IS_IOS, IS_MAC_OS, IS_TOUCH_ENV } from '../../../util/environment';
|
import { IS_IOS, IS_MAC_OS, IS_TOUCH_ENV } from '../../../util/environment';
|
||||||
import { pick } from '../../../util/iteratees';
|
import { pick } from '../../../util/iteratees';
|
||||||
|
import { setTimeFormat } from '../../../util/langProvider';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||||
@ -17,7 +18,7 @@ import useHistoryBack from '../../../hooks/useHistoryBack';
|
|||||||
import ListItem from '../../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
import RangeSlider from '../../ui/RangeSlider';
|
import RangeSlider from '../../ui/RangeSlider';
|
||||||
import Checkbox from '../../ui/Checkbox';
|
import Checkbox from '../../ui/Checkbox';
|
||||||
import RadioGroup from '../../ui/RadioGroup';
|
import RadioGroup, { IRadioOption } from '../../ui/RadioGroup';
|
||||||
import SettingsStickerSet from './SettingsStickerSet';
|
import SettingsStickerSet from './SettingsStickerSet';
|
||||||
import StickerSetModal from '../../common/StickerSetModal.async';
|
import StickerSetModal from '../../common/StickerSetModal.async';
|
||||||
|
|
||||||
@ -38,7 +39,8 @@ type StateProps = Pick<ISettings, (
|
|||||||
'shouldAutoPlayGifs' |
|
'shouldAutoPlayGifs' |
|
||||||
'shouldAutoPlayVideos' |
|
'shouldAutoPlayVideos' |
|
||||||
'shouldSuggestStickers' |
|
'shouldSuggestStickers' |
|
||||||
'shouldLoopStickers'
|
'shouldLoopStickers' |
|
||||||
|
'timeFormat'
|
||||||
)> & {
|
)> & {
|
||||||
stickerSetIds?: string[];
|
stickerSetIds?: string[];
|
||||||
stickerSetsById?: Record<string, ApiStickerSet>;
|
stickerSetsById?: Record<string, ApiStickerSet>;
|
||||||
@ -54,6 +56,14 @@ const ANIMATION_LEVEL_OPTIONS = [
|
|||||||
'Lots of Stuff',
|
'Lots of Stuff',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const TIME_FORMAT_OPTIONS: IRadioOption[] = [{
|
||||||
|
label: '12-hour',
|
||||||
|
value: '12h',
|
||||||
|
}, {
|
||||||
|
label: '24-hour',
|
||||||
|
value: '24h',
|
||||||
|
}];
|
||||||
|
|
||||||
const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
|
const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||||
isActive,
|
isActive,
|
||||||
onScreenSelect,
|
onScreenSelect,
|
||||||
@ -71,6 +81,7 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
shouldAutoPlayVideos,
|
shouldAutoPlayVideos,
|
||||||
shouldSuggestStickers,
|
shouldSuggestStickers,
|
||||||
shouldLoopStickers,
|
shouldLoopStickers,
|
||||||
|
timeFormat,
|
||||||
setSettingOption,
|
setSettingOption,
|
||||||
loadStickerSets,
|
loadStickerSets,
|
||||||
loadAddedStickers,
|
loadAddedStickers,
|
||||||
@ -121,6 +132,12 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
setSettingOption({ messageTextSize: newSize });
|
setSettingOption({ messageTextSize: newSize });
|
||||||
}, [setSettingOption]);
|
}, [setSettingOption]);
|
||||||
|
|
||||||
|
const handleTimeFormatChange = useCallback((value: string) => {
|
||||||
|
setTimeFormat(value as TimeFormat, () => {
|
||||||
|
setSettingOption({ timeFormat: value });
|
||||||
|
});
|
||||||
|
}, [setSettingOption]);
|
||||||
|
|
||||||
const handleStickerSetClick = useCallback((value: ApiSticker) => {
|
const handleStickerSetClick = useCallback((value: ApiSticker) => {
|
||||||
setSticker(value);
|
setSticker(value);
|
||||||
openModal();
|
openModal();
|
||||||
@ -153,6 +170,18 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="settings-item">
|
||||||
|
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
Time Format
|
||||||
|
</h4>
|
||||||
|
<RadioGroup
|
||||||
|
name="timeformat"
|
||||||
|
options={TIME_FORMAT_OPTIONS}
|
||||||
|
selected={timeFormat}
|
||||||
|
onChange={handleTimeFormatChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="settings-item">
|
<div className="settings-item">
|
||||||
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
|
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
Animation Level
|
Animation Level
|
||||||
@ -274,6 +303,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
'shouldLoopStickers',
|
'shouldLoopStickers',
|
||||||
'isSensitiveEnabled',
|
'isSensitiveEnabled',
|
||||||
'canChangeSensitive',
|
'canChangeSensitive',
|
||||||
|
'timeFormat',
|
||||||
]),
|
]),
|
||||||
stickerSetIds: global.stickers.added.setIds,
|
stickerSetIds: global.stickers.added.setIds,
|
||||||
stickerSetsById: global.stickers.setsById,
|
stickerSetsById: global.stickers.setsById,
|
||||||
|
|||||||
@ -42,9 +42,7 @@ const HistoryCalendar: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
return {
|
return { selectedAt: global.historyCalendarSelectedAt };
|
||||||
selectedAt: global.historyCalendarSelectedAt,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||||
'searchMessagesByDate', 'closeHistoryCalendar',
|
'searchMessagesByDate', 'closeHistoryCalendar',
|
||||||
|
|||||||
@ -34,7 +34,13 @@ type StateProps = {
|
|||||||
type DispatchProps = Pick<GlobalActions, 'closeMediaViewer' | 'focusMessage'>;
|
type DispatchProps = Pick<GlobalActions, 'closeMediaViewer' | 'focusMessage'>;
|
||||||
|
|
||||||
const SenderInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
const SenderInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||||
chatId, messageId, sender, isAvatar, message, closeMediaViewer, focusMessage,
|
chatId,
|
||||||
|
messageId,
|
||||||
|
sender,
|
||||||
|
isAvatar,
|
||||||
|
message,
|
||||||
|
closeMediaViewer,
|
||||||
|
focusMessage,
|
||||||
}) => {
|
}) => {
|
||||||
const handleFocusMessage = useCallback(() => {
|
const handleFocusMessage = useCallback(() => {
|
||||||
closeMediaViewer();
|
closeMediaViewer();
|
||||||
@ -62,7 +68,9 @@ const SenderInfo: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
{senderTitle && renderText(senderTitle)}
|
{senderTitle && renderText(senderTitle)}
|
||||||
</div>
|
</div>
|
||||||
<div className="date" dir="auto">
|
<div className="date" dir="auto">
|
||||||
{isAvatar ? lang('lng_mediaview_profile_photo') : formatMediaDateTime(lang, message!.date * 1000, true)}
|
{isAvatar
|
||||||
|
? lang('lng_mediaview_profile_photo')
|
||||||
|
: formatMediaDateTime(lang, message!.date * 1000, true)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1054,7 +1054,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
||||||
const messageWithActualBotKeyboard = isChatWithBot && selectNewestMessageWithBotKeyboardButtons(global, chatId);
|
const messageWithActualBotKeyboard = isChatWithBot && selectNewestMessageWithBotKeyboardButtons(global, chatId);
|
||||||
const scheduledIds = selectScheduledIds(global, chatId);
|
const scheduledIds = selectScheduledIds(global, chatId);
|
||||||
const { language } = global.settings.byKey;
|
const { language, shouldSuggestStickers } = global.settings.byKey;
|
||||||
const baseEmojiKeywords = global.emojiKeywords[BASE_EMOJI_KEYWORD_LANG];
|
const baseEmojiKeywords = global.emojiKeywords[BASE_EMOJI_KEYWORD_LANG];
|
||||||
const emojiKeywords = language !== BASE_EMOJI_KEYWORD_LANG ? global.emojiKeywords[language] : undefined;
|
const emojiKeywords = language !== BASE_EMOJI_KEYWORD_LANG ? global.emojiKeywords[language] : undefined;
|
||||||
const botKeyboardMessageId = messageWithActualBotKeyboard ? messageWithActualBotKeyboard.id : undefined;
|
const botKeyboardMessageId = messageWithActualBotKeyboard ? messageWithActualBotKeyboard.id : undefined;
|
||||||
@ -1090,7 +1090,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
usersById: global.users.byId,
|
usersById: global.users.byId,
|
||||||
lastSyncTime: global.lastSyncTime,
|
lastSyncTime: global.lastSyncTime,
|
||||||
contentToBeScheduled: global.messages.contentToBeScheduled,
|
contentToBeScheduled: global.messages.contentToBeScheduled,
|
||||||
shouldSuggestStickers: global.settings.byKey.shouldSuggestStickers,
|
shouldSuggestStickers,
|
||||||
recentEmojis: global.recentEmojis,
|
recentEmojis: global.recentEmojis,
|
||||||
baseEmojiKeywords: baseEmojiKeywords?.keywords,
|
baseEmojiKeywords: baseEmojiKeywords?.keywords,
|
||||||
emojiKeywords: emojiKeywords?.keywords,
|
emojiKeywords: emojiKeywords?.keywords,
|
||||||
|
|||||||
@ -38,7 +38,7 @@ const MessageMeta: FC<OwnProps> = ({
|
|||||||
)}
|
)}
|
||||||
<span className="message-time">
|
<span className="message-time">
|
||||||
{message.isEdited && `${lang('EditedMessage')} `}
|
{message.isEdited && `${lang('EditedMessage')} `}
|
||||||
{formatTime(message.date * 1000)}
|
{formatTime(message.date * 1000, lang)}
|
||||||
</span>
|
</span>
|
||||||
{outgoingStatus && (
|
{outgoingStatus && (
|
||||||
<MessageOutgoingStatus status={outgoingStatus} />
|
<MessageOutgoingStatus status={outgoingStatus} />
|
||||||
|
|||||||
@ -58,6 +58,11 @@
|
|||||||
|
|
||||||
.Tab {
|
.Tab {
|
||||||
padding: 1rem .75rem;
|
padding: 1rem .75rem;
|
||||||
|
|
||||||
|
span {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
bottom: -1rem;
|
bottom: -1rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -126,6 +126,7 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
isRestricted,
|
isRestricted,
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
activeDownloadIds,
|
activeDownloadIds,
|
||||||
|
serverTimeOffset,
|
||||||
setLocalMediaSearchType,
|
setLocalMediaSearchType,
|
||||||
loadMoreMembers,
|
loadMoreMembers,
|
||||||
searchMediaMessagesLocal,
|
searchMediaMessagesLocal,
|
||||||
@ -135,7 +136,6 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
focusMessage,
|
focusMessage,
|
||||||
loadProfilePhotos,
|
loadProfilePhotos,
|
||||||
setNewChatMembersDialogState,
|
setNewChatMembersDialogState,
|
||||||
serverTimeOffset,
|
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import React, { FC, useCallback, memo } from '../../lib/teact/teact';
|
|||||||
|
|
||||||
import Radio from './Radio';
|
import Radio from './Radio';
|
||||||
|
|
||||||
type IRadioOption = {
|
export type IRadioOption = {
|
||||||
label: string;
|
label: string;
|
||||||
subLabel?: string;
|
subLabel?: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
|||||||
@ -145,6 +145,7 @@ export const INITIAL_STATE: GlobalState = {
|
|||||||
shouldSuggestStickers: true,
|
shouldSuggestStickers: true,
|
||||||
shouldLoopStickers: true,
|
shouldLoopStickers: true,
|
||||||
language: 'en',
|
language: 'en',
|
||||||
|
timeFormat: '24h',
|
||||||
},
|
},
|
||||||
themes: {
|
themes: {
|
||||||
light: {
|
light: {
|
||||||
|
|||||||
@ -185,7 +185,9 @@ export function getAllowedAttachmentOptions(chat?: ApiChat, isChatWithBot = fals
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageSendingRestrictionReason(
|
export function getMessageSendingRestrictionReason(
|
||||||
lang: LangFn, currentUserBannedRights?: ApiChatBannedRights, defaultBannedRights?: ApiChatBannedRights,
|
lang: LangFn,
|
||||||
|
currentUserBannedRights?: ApiChatBannedRights,
|
||||||
|
defaultBannedRights?: ApiChatBannedRights,
|
||||||
) {
|
) {
|
||||||
if (currentUserBannedRights?.sendMessages) {
|
if (currentUserBannedRights?.sendMessages) {
|
||||||
const { untilDate } = currentUserBannedRights;
|
const { untilDate } = currentUserBannedRights;
|
||||||
@ -194,7 +196,7 @@ export function getMessageSendingRestrictionReason(
|
|||||||
'Channel.Persmission.Denied.SendMessages.Until',
|
'Channel.Persmission.Denied.SendMessages.Until',
|
||||||
lang(
|
lang(
|
||||||
'formatDateAtTime',
|
'formatDateAtTime',
|
||||||
[formatDateToString(new Date(untilDate * 1000), lang.code), formatTime(untilDate * 1000)],
|
[formatDateToString(new Date(untilDate * 1000), lang.code), formatTime(untilDate * 1000, lang)],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: lang('Channel.Persmission.Denied.SendMessages.Forever');
|
: lang('Channel.Persmission.Denied.SendMessages.Forever');
|
||||||
|
|||||||
@ -128,7 +128,7 @@ export function getUserStatus(lang: LangFn, user: ApiUser, serverTimeOffset: num
|
|||||||
}
|
}
|
||||||
|
|
||||||
// other
|
// other
|
||||||
return lang('LastSeen.TodayAt', formatTime(wasOnlineDate));
|
return lang('LastSeen.TodayAt', formatTime(wasOnlineDate, lang));
|
||||||
}
|
}
|
||||||
|
|
||||||
// yesterday
|
// yesterday
|
||||||
@ -137,7 +137,7 @@ export function getUserStatus(lang: LangFn, user: ApiUser, serverTimeOffset: num
|
|||||||
yesterday.setHours(0, 0, 0, 0);
|
yesterday.setHours(0, 0, 0, 0);
|
||||||
const serverYesterday = new Date(yesterday.getTime() + serverTimeOffset * 1000);
|
const serverYesterday = new Date(yesterday.getTime() + serverTimeOffset * 1000);
|
||||||
if (wasOnlineDate > serverYesterday) {
|
if (wasOnlineDate > serverYesterday) {
|
||||||
return lang('LastSeen.YesterdayAt', formatTime(wasOnlineDate));
|
return lang('LastSeen.YesterdayAt', formatTime(wasOnlineDate, lang));
|
||||||
}
|
}
|
||||||
|
|
||||||
return lang('LastSeen.AtDate', formatFullDate(lang, wasOnlineDate));
|
return lang('LastSeen.AtDate', formatFullDate(lang, wasOnlineDate));
|
||||||
|
|||||||
@ -48,6 +48,8 @@ export type LangCode = (
|
|||||||
| 'tr' | 'uk' | 'uz'
|
| 'tr' | 'uk' | 'uz'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type TimeFormat = '24h' | '12h';
|
||||||
|
|
||||||
export interface ISettings extends NotifySettings, Record<string, any> {
|
export interface ISettings extends NotifySettings, Record<string, any> {
|
||||||
theme: ThemeKey;
|
theme: ThemeKey;
|
||||||
shouldUseSystemTheme: boolean;
|
shouldUseSystemTheme: boolean;
|
||||||
@ -67,6 +69,7 @@ export interface ISettings extends NotifySettings, Record<string, any> {
|
|||||||
language: LangCode;
|
language: LangCode;
|
||||||
isSensitiveEnabled?: boolean;
|
isSensitiveEnabled?: boolean;
|
||||||
canChangeSensitive?: boolean;
|
canChangeSensitive?: boolean;
|
||||||
|
timeFormat: TimeFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiPrivacySettings {
|
export interface ApiPrivacySettings {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ const MONTHS_FULL_LOWERCASE = MONTHS_FULL.map((month) => month.toLowerCase());
|
|||||||
const MIN_SEARCH_YEAR = 2015;
|
const MIN_SEARCH_YEAR = 2015;
|
||||||
const MAX_DAY_IN_MONTH = 31;
|
const MAX_DAY_IN_MONTH = 31;
|
||||||
const MAX_MONTH_IN_YEAR = 12;
|
const MAX_MONTH_IN_YEAR = 12;
|
||||||
export const MILISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
|
export const MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
export function getDayStart(datetime: number | Date) {
|
export function getDayStart(datetime: number | Date) {
|
||||||
const date = new Date(datetime);
|
const date = new Date(datetime);
|
||||||
@ -31,12 +31,17 @@ function toIsoString(date: Date) {
|
|||||||
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
|
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatTime(datetime: number | Date) {
|
export function formatTime(datetime: number | Date, lang: LangFn) {
|
||||||
const date = typeof datetime === 'number' ? new Date(datetime) : datetime;
|
const date = typeof datetime === 'number' ? new Date(datetime) : datetime;
|
||||||
const hours = String(date.getHours()).padStart(2, '0');
|
const timeFormat = lang.timeFormat || '24h';
|
||||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
||||||
|
|
||||||
return `${hours}:${minutes}`;
|
const time = date.toLocaleTimeString(lang.code, {
|
||||||
|
hour12: timeFormat === '12h',
|
||||||
|
hour: timeFormat === '12h' ? 'numeric' : '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
});
|
||||||
|
|
||||||
|
return timeFormat === '12h' ? time.replace(/^0:/, '12:') : time;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatPastTimeShort(lang: LangFn, datetime: number | Date) {
|
export function formatPastTimeShort(lang: LangFn, datetime: number | Date) {
|
||||||
@ -44,7 +49,7 @@ export function formatPastTimeShort(lang: LangFn, datetime: number | Date) {
|
|||||||
|
|
||||||
const today = getDayStart(new Date());
|
const today = getDayStart(new Date());
|
||||||
if (date >= today) {
|
if (date >= today) {
|
||||||
return formatTime(date);
|
return formatTime(date, lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
const weekAgo = new Date(today);
|
const weekAgo = new Date(today);
|
||||||
@ -132,10 +137,14 @@ function formatDate(lang: LangFn, date: Date, format: string) {
|
|||||||
.replace('yyyy', String(date.getFullYear()));
|
.replace('yyyy', String(date.getFullYear()));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatMediaDateTime(lang: LangFn, datetime: number | Date, isUpperFirst?: boolean) {
|
export function formatMediaDateTime(
|
||||||
|
lang: LangFn,
|
||||||
|
datetime: number | Date,
|
||||||
|
isUpperFirst?: boolean,
|
||||||
|
) {
|
||||||
const date = typeof datetime === 'number' ? new Date(datetime) : datetime;
|
const date = typeof datetime === 'number' ? new Date(datetime) : datetime;
|
||||||
|
|
||||||
return `${formatHumanDate(lang, date, true, undefined, isUpperFirst)}, ${formatTime(date)}`;
|
return `${formatHumanDate(lang, date, true, undefined, isUpperFirst)}, ${formatTime(date, lang)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatMediaDuration(duration: number, maxValue?: number) {
|
export function formatMediaDuration(duration: number, maxValue?: number) {
|
||||||
@ -231,7 +240,7 @@ export function parseDateString(query = ''): string | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function timestampPlusDay(timestamp: number) {
|
export function timestampPlusDay(timestamp: number) {
|
||||||
return timestamp + MILISECONDS_IN_DAY / 1000;
|
return timestamp + MILLISECONDS_IN_DAY / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
function lowerFirst(str: string) {
|
function lowerFirst(str: string) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { getGlobal } from '../lib/teact/teactn';
|
import { getGlobal } from '../lib/teact/teactn';
|
||||||
|
|
||||||
import { ApiLangPack, ApiLangString } from '../api/types';
|
import { ApiLangPack, ApiLangString } from '../api/types';
|
||||||
import { LangCode } from '../types';
|
import { LangCode, TimeFormat } from '../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_LANG_CODE, DEFAULT_LANG_PACK, LANG_CACHE_NAME, LANG_PACKS,
|
DEFAULT_LANG_CODE, DEFAULT_LANG_PACK, LANG_CACHE_NAME, LANG_PACKS,
|
||||||
@ -16,6 +16,7 @@ interface LangFn {
|
|||||||
|
|
||||||
isRtl?: boolean;
|
isRtl?: boolean;
|
||||||
code?: LangCode;
|
code?: LangCode;
|
||||||
|
timeFormat?: TimeFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SUBSTITUTION_REGEX = /%\d?\$?[sdf@]/g;
|
const SUBSTITUTION_REGEX = /%\d?\$?[sdf@]/g;
|
||||||
@ -57,6 +58,7 @@ const {
|
|||||||
export { addCallback, removeCallback };
|
export { addCallback, removeCallback };
|
||||||
|
|
||||||
let currentLangCode: string | undefined;
|
let currentLangCode: string | undefined;
|
||||||
|
let currentTimeFormat: TimeFormat | undefined;
|
||||||
|
|
||||||
export const getTranslation: LangFn = (key: string, value?: any, format?: 'i') => {
|
export const getTranslation: LangFn = (key: string, value?: any, format?: 'i') => {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
@ -124,10 +126,30 @@ export async function setLanguage(langCode: LangCode, callback?: NoneToVoidFunct
|
|||||||
langPack = newLangPack;
|
langPack = newLangPack;
|
||||||
document.documentElement.lang = langCode;
|
document.documentElement.lang = langCode;
|
||||||
|
|
||||||
const { languages } = getGlobal().settings.byKey;
|
const { languages, timeFormat } = getGlobal().settings.byKey;
|
||||||
const langInfo = languages?.find((l) => l.langCode === langCode);
|
const langInfo = languages?.find((l) => l.langCode === langCode);
|
||||||
getTranslation.isRtl = Boolean(langInfo?.rtl);
|
getTranslation.isRtl = Boolean(langInfo?.rtl);
|
||||||
getTranslation.code = langCode;
|
getTranslation.code = langCode;
|
||||||
|
getTranslation.timeFormat = timeFormat;
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
runCallbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setTimeFormat(timeFormat: TimeFormat, callback?: NoneToVoidFunction) {
|
||||||
|
if (timeFormat && timeFormat === currentTimeFormat) {
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTimeFormat = timeFormat;
|
||||||
|
getTranslation.timeFormat = timeFormat;
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user