Settings / General: Support time formats (#1524)

This commit is contained in:
Alexander Zinchuk 2021-11-05 21:56:39 +03:00
parent f8640762af
commit 6028e0b4ce
20 changed files with 128 additions and 38 deletions

View File

@ -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">&bull;</span> <span className="bullet">&bull;</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>

View File

@ -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);

View File

@ -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);

View File

@ -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']),

View File

@ -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);

View File

@ -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">

View File

@ -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,

View File

@ -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',

View File

@ -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>

View File

@ -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,

View File

@ -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} />

View File

@ -58,6 +58,11 @@
.Tab { .Tab {
padding: 1rem .75rem; padding: 1rem .75rem;
span {
white-space: nowrap;
}
i { i {
bottom: -1rem; bottom: -1rem;
} }

View File

@ -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);

View File

@ -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;

View File

@ -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: {

View File

@ -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');

View File

@ -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));

View File

@ -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 {

View File

@ -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) {

View File

@ -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();