Fix livestream countdown i18n

Ticket: 1228

## Code changes
- Pass through `getTimeAgoStr` so that the value gets localized.
- Pass the simpler `number` around instead of the `moment` object for better memoization.

## Notable differences
Due to how `getTimeAgoStr` is written, we now get to see the time actually counting down, vs "in a few seconds" currently in production.  I think the counting-down behavior was the original intentional, since a 1s timer was used (otherwise, a 1-minute timer could be used) ... or maybe not since streams may not start on the dot.
This commit is contained in:
infinite-persistence 2022-03-31 15:21:28 +08:00 committed by Thomas Zarebczan
parent 321a6901b4
commit 3b98f73a0f
4 changed files with 41 additions and 30 deletions

View file

@ -30,7 +30,7 @@ type Props = {
claim: ?StreamClaim, claim: ?StreamClaim,
hideComments: boolean, hideComments: boolean,
isCurrentClaimLive: boolean, isCurrentClaimLive: boolean,
release: any, releaseTimeMs: number,
showLivestream: boolean, showLivestream: boolean,
showScheduledInfo: boolean, showScheduledInfo: boolean,
uri: string, uri: string,
@ -44,7 +44,7 @@ export default function LivestreamLayout(props: Props) {
claim, claim,
hideComments, hideComments,
isCurrentClaimLive, isCurrentClaimLive,
release, releaseTimeMs,
showLivestream, showLivestream,
showScheduledInfo, showScheduledInfo,
uri, uri,
@ -72,7 +72,7 @@ export default function LivestreamLayout(props: Props) {
<div className={PRIMARY_PLAYER_WRAPPER_CLASS}> <div className={PRIMARY_PLAYER_WRAPPER_CLASS}>
<FileRenderInitiator <FileRenderInitiator
uri={claim.canonical_url} uri={claim.canonical_url}
customAction={showScheduledInfo && <LivestreamScheduledInfo release={release} />} customAction={showScheduledInfo && <LivestreamScheduledInfo releaseTimeMs={releaseTimeMs} />}
/> />
</div> </div>

View file

@ -5,29 +5,36 @@ import * as ICONS from 'constants/icons';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import moment from 'moment'; import moment from 'moment';
import 'scss/component/livestream-scheduled-info.scss'; import 'scss/component/livestream-scheduled-info.scss';
import I18nMessage from 'component/i18nMessage';
import { getTimeAgoStr } from 'util/time';
type Props = { type Props = {
release: any, releaseTimeMs: number,
}; };
export default function LivestreamScheduledInfo(props: Props) { export default function LivestreamScheduledInfo(props: Props) {
const { release } = props; const { releaseTimeMs } = props;
const [startDateFromNow, setStartDateFromNow] = useState(release.fromNow()); const [startDateFromNow, setStartDateFromNow] = useState('');
const [inPast, setInPast] = useState('pending'); const [inPast, setInPast] = useState('pending');
useEffect(() => { useEffect(() => {
const calcTime = () => { const calcTime = () => {
setStartDateFromNow(release.fromNow()); const zeroDurationStr = '---';
setInPast(release.isBefore(moment())); const timeAgoStr = getTimeAgoStr(releaseTimeMs, true, true, zeroDurationStr);
};
calcTime();
const intervalId = setInterval(calcTime, 1000);
return () => {
clearInterval(intervalId);
};
}, [release]);
const startDate = release.format('MMMM Do, h:mm a'); if (timeAgoStr === zeroDurationStr) {
setInPast(true);
} else {
setStartDateFromNow(timeAgoStr);
setInPast(releaseTimeMs < Date.now());
}
};
const intervalId = setInterval(calcTime, 1000);
return () => clearInterval(intervalId);
}, [releaseTimeMs]);
const startDate = moment(releaseTimeMs).format('MMMM Do, h:mm a');
return ( return (
inPast !== 'pending' && ( inPast !== 'pending' && (
@ -36,7 +43,7 @@ export default function LivestreamScheduledInfo(props: Props) {
<p className={'livestream-scheduled__time'}> <p className={'livestream-scheduled__time'}>
{!inPast && ( {!inPast && (
<span> <span>
<span>{__('Live %time_date%', { time_date: startDateFromNow })}</span> <I18nMessage tokens={{ time_date: startDateFromNow }}>Live %time_date%</I18nMessage>
<br /> <br />
<span className={'livestream-scheduled__date'}>{startDate}</span> <span className={'livestream-scheduled__date'}>{startDate}</span>
</span> </span>

View file

@ -59,12 +59,11 @@ export default function LivestreamPage(props: Props) {
const isCurrentClaimLive = isChannelBroadcasting && activeLivestreamForChannel.claimId === claimId; const isCurrentClaimLive = isChannelBroadcasting && activeLivestreamForChannel.claimId === claimId;
const livestreamChannelId = channelClaimId || ''; const livestreamChannelId = channelClaimId || '';
// $FlowFixMe const releaseTime: moment = moment.unix(claim?.value?.release_time || 0);
const release = moment.unix(claim.value.release_time);
const stringifiedClaim = JSON.stringify(claim); const stringifiedClaim = JSON.stringify(claim);
React.useEffect(() => { React.useEffect(() => {
// TODO: This should not be needed one we unify the livestream player (?) // TODO: This should not be needed once we unify the livestream player (?)
analytics.playerLoadedEvent('livestream', false); analytics.playerLoadedEvent('livestream', false);
}, []); }, []);
@ -88,10 +87,10 @@ export default function LivestreamPage(props: Props) {
}, [channelUrl, claim, doCommentSocketConnect, uri]); }, [channelUrl, claim, doCommentSocketConnect, uri]);
const claimReleaseStartingSoonStatic = () => const claimReleaseStartingSoonStatic = () =>
release.isBetween(moment(), moment().add(LIVESTREAM_STARTS_SOON_BUFFER, 'minutes')); releaseTime.isBetween(moment(), moment().add(LIVESTREAM_STARTS_SOON_BUFFER, 'minutes'));
const claimReleaseStartedRecentlyStatic = () => const claimReleaseStartedRecentlyStatic = () =>
release.isBetween(moment().subtract(LIVESTREAM_STARTED_RECENTLY_BUFFER, 'minutes'), moment()); releaseTime.isBetween(moment().subtract(LIVESTREAM_STARTED_RECENTLY_BUFFER, 'minutes'), moment());
// Find out current channels status + active live claim every 30 seconds (or 15 if not live) // Find out current channels status + active live claim every 30 seconds (or 15 if not live)
const fasterPoll = !isCurrentClaimLive && (claimReleaseStartingSoonStatic() || claimReleaseStartedRecentlyStatic()); const fasterPoll = !isCurrentClaimLive && (claimReleaseStartingSoonStatic() || claimReleaseStartedRecentlyStatic());
@ -105,14 +104,14 @@ export default function LivestreamPage(props: Props) {
React.useEffect(() => { React.useEffect(() => {
if (!isInitialized) return; if (!isInitialized) return;
const claimReleaseInFuture = () => release.isAfter(); const claimReleaseInFuture = () => releaseTime.isAfter();
const claimReleaseInPast = () => release.isBefore(); const claimReleaseInPast = () => releaseTime.isBefore();
const claimReleaseStartingSoon = () => const claimReleaseStartingSoon = () =>
release.isBetween(moment(), moment().add(LIVESTREAM_STARTS_SOON_BUFFER, 'minutes')); releaseTime.isBetween(moment(), moment().add(LIVESTREAM_STARTS_SOON_BUFFER, 'minutes'));
const claimReleaseStartedRecently = () => const claimReleaseStartedRecently = () =>
release.isBetween(moment().subtract(LIVESTREAM_STARTED_RECENTLY_BUFFER, 'minutes'), moment()); releaseTime.isBetween(moment().subtract(LIVESTREAM_STARTED_RECENTLY_BUFFER, 'minutes'), moment());
const checkShowLivestream = () => const checkShowLivestream = () =>
isChannelBroadcasting && isChannelBroadcasting &&
@ -141,7 +140,7 @@ export default function LivestreamPage(props: Props) {
} }
return () => clearInterval(intervalId); return () => clearInterval(intervalId);
}, [chatDisabled, isChannelBroadcasting, release, isCurrentClaimLive, isInitialized]); }, [chatDisabled, isChannelBroadcasting, releaseTime, isCurrentClaimLive, isInitialized]);
React.useEffect(() => { React.useEffect(() => {
if (uri && stringifiedClaim) { if (uri && stringifiedClaim) {
@ -181,7 +180,7 @@ export default function LivestreamPage(props: Props) {
<LivestreamLayout <LivestreamLayout
uri={uri} uri={uri}
hideComments={hideComments} hideComments={hideComments}
release={release} releaseTimeMs={releaseTime.unix() * 1000}
isCurrentClaimLive={isCurrentClaimLive} isCurrentClaimLive={isCurrentClaimLive}
showLivestream={showLivestream} showLivestream={showLivestream}
showScheduledInfo={showScheduledInfo} showScheduledInfo={showScheduledInfo}

View file

@ -36,7 +36,12 @@ export function hmsToSeconds(str: string) {
// Only intended use of future dates is for claims, in case of scheduled // Only intended use of future dates is for claims, in case of scheduled
// publishes or livestreams, used in util/formatAriaLabel // publishes or livestreams, used in util/formatAriaLabel
export function getTimeAgoStr(date: any, showFutureDate?: boolean, genericSecondsString?: boolean) { export function getTimeAgoStr(
date: any,
showFutureDate?: boolean,
genericSecondsString?: boolean,
zeroDurationStr: string = 'Just now'
) {
const suffixList = ['years', 'months', 'days', 'hours', 'minutes', 'seconds']; const suffixList = ['years', 'months', 'days', 'hours', 'minutes', 'seconds'];
let duration = 0; let duration = 0;
let suffix = ''; let suffix = '';
@ -59,7 +64,7 @@ export function getTimeAgoStr(date: any, showFutureDate?: boolean, genericSecond
str = suffix === 'seconds' ? 'in a few seconds' : 'in %duration% ' + suffix; str = suffix === 'seconds' ? 'in a few seconds' : 'in %duration% ' + suffix;
duration = duration * -1; duration = duration * -1;
} else if (duration <= 0) { } else if (duration <= 0) {
str = 'Just now'; str = zeroDurationStr;
} else { } else {
str = suffix === 'seconds' && genericSecondsString ? 'A few seconds ago' : '%duration% ' + suffix + ' ago'; str = suffix === 'seconds' && genericSecondsString ? 'A few seconds ago' : '%duration% ' + suffix + ' ago';
} }