Add relative time strings

This commit is contained in:
Samuel Elliott 2023-01-29 14:14:42 +00:00
parent a661f91efb
commit b9b9f6a8d7
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6
6 changed files with 83 additions and 27 deletions

View File

@ -48,7 +48,7 @@ export default function FriendWindow(props: FriendProps) {
if (!user || !friend || discord_presence_source_state !== RequestState.LOADED) {
return <Root title={friend?.name} titleUser={user ?? undefined}
autoresize={!!user && discord_presence_source_state === RequestState.LOADED}
i18nNamespace="friend_window"
i18nNamespace={['friend_window', 'time_since']}
>
<View style={styles.loading}>
<ActivityIndicator size="large" color={'#' + (accent_colour ?? DEFAULT_ACCENT_COLOUR)} />
@ -62,7 +62,7 @@ export default function FriendWindow(props: FriendProps) {
const can_see_user_presence = user.nsoAccount.user.permissions.presence === PresencePermissions.FRIENDS ||
(user.nsoAccount.user.permissions.presence === PresencePermissions.FAVORITE_FRIENDS && friend.isFavoriteFriend);
return <Root title={friend.name} titleUser={user} autoresize i18nNamespace="friend_window">
return <Root title={friend.name} titleUser={user} autoresize i18nNamespace={['friend_window', 'time_since']}>
<Friend
friend={friend} canSeeUserPresence={can_see_user_presence}
showDiscordPresenceSetup={discord_presence_active || !!friend.presence.updatedAt || false}
@ -160,7 +160,7 @@ function FriendPresence(props: {
const { t, i18n } = useTranslation('friend_window');
const logout = props.presence.logoutAt ? new Date(props.presence.logoutAt * 1000) : null;
const since_logout = useTimeSince(logout ?? new Date(0));
const since_logout = useTimeSince(logout ?? new Date(0), false, i18n.getFixedT(null, 'time_since'));
const game = 'name' in props.presence.game ? props.presence.game : null;
if ((props.presence.state === PresenceState.ONLINE || props.presence.state === PresenceState.PLAYING) && game) {
@ -194,7 +194,12 @@ function FriendPresenceGame(props: {
<Text style={[styles.gameName, theme.text]}>{props.game.name}</Text>
{props.game.sysDescription ? <Text style={[styles.gameActivity, theme.text]}>{props.game.sysDescription}</Text> : null}
<Text style={[styles.gameTotalPlayTime, theme.text]}>
{t('game_played_for', {duration: hrduration(props.game.totalPlayTime)})}
{props.game.totalPlayTime >= 60 ?
props.game.totalPlayTime % 60 ?
t('game_played_for_hm', {hours: Math.floor(props.game.totalPlayTime / 60),
minutes: props.game.totalPlayTime % 60}) :
t('game_played_for_h', {hours: props.game.totalPlayTime / 60}) :
t('game_played_for_m', {minutes: props.game.totalPlayTime})}
</Text>
<Text style={[styles.gameFirstPlayed, theme.text]}>
{first_played ? t('game_first_played', {

View File

@ -98,7 +98,7 @@ function FriendPresence(props: {
const { t, i18n } = useTranslation('main_window', { keyPrefix: 'friends_section' });
const logout = props.presence.logoutAt ? new Date(props.presence.logoutAt * 1000) : null;
const since_logout = useTimeSince(logout ?? new Date(0), true);
const since_logout = useTimeSince(logout ?? new Date(0), true, i18n.getFixedT(null, 'time_since'));
if (props.presence.state === PresenceState.ONLINE || props.presence.state === PresenceState.PLAYING) {
return <Text style={[styles.presenceText, theme.text, styles.presenceTextOnline]}>{t('presence_playing')}</Text>;

View File

@ -36,7 +36,7 @@ export default function App(props: AppProps) {
titleUser={selectedUser}
vibrancy={props.vibrancy}
style={[styles.app, !props.vibrancy ? theme.appNoVibrancy : null]}
i18nNamespace="main_window"
i18nNamespace={['main_window', 'time_since']}
>
<Sidebar users={users} selectedUser={selectedUserId} onSelectUser={setSelectedUserId}
insetTitleBarControls={props.insetTitleBarControls}

View File

@ -1,7 +1,7 @@
import { EventEmitter } from 'node:events';
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
import { ColorSchemeName, I18nManager, LayoutChangeEvent, Platform, StyleProp, StyleSheet, useColorScheme, View, ViewStyle } from 'react-native';
import { i18n } from 'i18next';
import { ColorSchemeName, LayoutChangeEvent, Platform, StyleProp, StyleSheet, useColorScheme, View, ViewStyle } from 'react-native';
import { i18n, TFunction } from 'i18next';
import { I18nextProvider, initReactI18next } from 'react-i18next';
import type { User as DiscordUser } from 'discord-rpc';
import { ErrorResponse } from '../../api/util.js';
@ -305,10 +305,10 @@ export function useActiveDiscordUser() {
return user;
}
export function useTimeSince(time: Date, short = false) {
export function useTimeSince(time: Date, short = false, t?: TFunction) {
const [now, setNow] = useState(Date.now());
const [since, update_in] = getTimeSince(time, now, short ? short_time_since_intervals : time_since_intervals);
const [since, update_in] = getTimeSince(time, now, short ? short_time_since_intervals : time_since_intervals, t);
const update_at = Date.now() + update_in;
useEffect(() => {
@ -323,24 +323,28 @@ interface TimeSinceInterval {
interval: number;
max: number;
string: (count: number) => string;
key?: string;
}
const time_since_intervals: TimeSinceInterval[] = [
{interval: 1000, max: 10, string: () => 'just now'},
{interval: 1000, max: 60, string: c => c + ' second' + (c === 1 ? '' : 's') + ' ago'},
{interval: 60 * 1000, max: 60, string: c => c + ' minute' + (c === 1 ? '' : 's') + ' ago'},
{interval: 60 * 60 * 1000, max: 24, string: c => c + ' hour' + (c === 1 ? '' : 's') + ' ago'},
{interval: 24 * 60 * 60 * 1000, max: Infinity, string: c => c + ' day' + (c === 1 ? '' : 's') + ' ago'},
{interval: 1000, max: 10, string: () => 'just now', key: 'default.now'},
{interval: 1000, max: 60, string: c => c + ' second' + (c === 1 ? '' : 's') + ' ago', key: 'default.seconds'},
{interval: 60 * 1000, max: 60, string: c => c + ' minute' + (c === 1 ? '' : 's') + ' ago', key: 'default.minutes'},
{interval: 60 * 60 * 1000, max: 24, string: c => c + ' hour' + (c === 1 ? '' : 's') + ' ago', key: 'default.hours'},
{interval: 24 * 60 * 60 * 1000, max: Infinity, string: c => c + ' day' + (c === 1 ? '' : 's') + ' ago', key: 'default.days'},
];
const short_time_since_intervals: TimeSinceInterval[] = [
{interval: 1000, max: 10, string: () => 'Just now'},
{interval: 1000, max: 60, string: c => c + ' sec' + (c === 1 ? '' : 's')},
{interval: 60 * 1000, max: 60, string: c => c + ' min' + (c === 1 ? '' : 's')},
{interval: 60 * 60 * 1000, max: 24, string: c => c + ' hr' + (c === 1 ? '' : 's')},
{interval: 24 * 60 * 60 * 1000, max: Infinity, string: c => c + ' day' + (c === 1 ? '' : 's')},
{interval: 1000, max: 10, string: () => 'Just now', key: 'default.now'},
{interval: 1000, max: 60, string: c => c + ' sec' + (c === 1 ? '' : 's'), key: 'short.seconds'},
{interval: 60 * 1000, max: 60, string: c => c + ' min' + (c === 1 ? '' : 's'), key: 'short.minutes'},
{interval: 60 * 60 * 1000, max: 24, string: c => c + ' hr' + (c === 1 ? '' : 's'), key: 'short.hours'},
{interval: 24 * 60 * 60 * 1000, max: Infinity, string: c => c + ' day' + (c === 1 ? '' : 's'), key: 'short.days'},
];
function getTimeSince(time: Date | number, now = Date.now(), intervals = time_since_intervals): [string, number] {
function getTimeSince(
time: Date | number, now = Date.now(),
intervals = time_since_intervals, t?: TFunction,
): [string, number] {
if (time instanceof Date) time = time.getTime();
const elapsed = Math.max(0, now - time);
@ -349,7 +353,10 @@ function getTimeSince(time: Date | number, now = Date.now(), intervals = time_si
for (const i of intervals) {
if (elapsed < i.max * i.interval || last === i) {
const count = Math.floor(elapsed / i.interval);
return [i.string.call(null, count), i.interval - (elapsed - (count * i.interval))];
return [
(t && i.key ? t(i.key, {count, defaultValue: ''}) : '') || i.string.call(null, count),
i.interval - (elapsed - (count * i.interval)),
];
}
}

View File

@ -69,7 +69,15 @@ export const menus = {
friend: {
presence_online: 'Online',
game_first_played: 'First played: {{date, datetime}}',
game_play_time: 'Play time: {{time, datetime}}',
game_play_time_h: 'Play time: $t(friend.hours, {"count": {{hours}}})',
game_play_time_hm: 'Play time: $t(friend.hours, {"count": {{hours}}}), $t(friend.minutes, {"count": {{minutes}}})',
game_play_time_m: 'Play time: $t(friend.minutes, {"count": {{minutes}}})',
hours_one: '{{count}} hour',
hours_other: '{{count}} hours',
minutes_one: '{{count}} minute',
minutes_other: '{{count}} minutes',
presence_inactive: 'Offline (console online)',
presence_offline: 'Offline',
presence_updated: 'Updated: {{date, datetime}}',
@ -91,6 +99,32 @@ export const handle_uri = {
cancel: 'Cancel',
};
export const time_since = {
default: {
now: 'just now',
seconds_one: '{{count}} second ago',
seconds_other: '{{count}} seconds ago',
minutes_one: '{{count}} minute ago',
minutes_other: '{{count}} minutes ago',
hours_one: '{{count}} hour ago',
hours_other: '{{count}} hours ago',
days_one: '{{count}} day ago',
days_other: '{{count}} days ago',
},
short: {
now: 'Just now',
seconds_one: '{{count}} sec',
seconds_other: '{{count}} secs',
minutes_one: '{{count}} min',
minutes_other: '{{count}} mins',
hours_one: '{{count}} hr',
hours_other: '{{count}} hrs',
days_one: '{{count}} day',
days_other: '{{count}} days',
},
};
export const main_window = {
sidebar: {
discord_active: 'Discord Rich Presence active',
@ -244,7 +278,14 @@ export const friend_window = {
presence_offline: 'Offline',
presence_last_seen: 'Last seen {{since_logout}}',
game_played_for: 'Played for {{duration}}',
game_played_for_h: 'Played for $t(hours, {"count": {{hours}}})',
game_played_for_hm: 'Played for $t(hours, {"count": {{hours}}}), $t(minutes, {"count": {{minutes}}})',
game_played_for_m: 'Played for $t(minutes, {"count": {{minutes}}})',
hours_one: '{{count}} hour',
hours_other: '{{count}} hours',
minutes_one: '{{count}} minute',
minutes_other: '{{count}} minutes',
game_first_played: 'First played {{date, datetime}}',
game_first_played_now: 'First played now',
game_title_id: 'Title ID',

View File

@ -316,9 +316,12 @@ function buildFriendMenu(app: App, user: NintendoAccountUser, nso: CurrentUser,
date: new Date(friend.presence.game.firstPlayedAt * 1000),
formatParams: { date: { dateStyle: 'short', timeStyle: 'medium' } },
})!, enabled: false}),
new MenuItem({label: t('game_play_time', {
time: hrduration(friend.presence.game.totalPlayTime),
})!, enabled: false}),
new MenuItem({label: friend.presence.game.totalPlayTime >= 60 ?
friend.presence.game.totalPlayTime % 60 ?
t('game_play_time_hm', {hours: Math.floor(friend.presence.game.totalPlayTime / 60),
minutes: friend.presence.game.totalPlayTime % 60})! :
t('game_play_time_h', {hours: friend.presence.game.totalPlayTime / 60})! :
t('game_play_time_m', {minutes: friend.presence.game.totalPlayTime})!, enabled: false}),
] : []),
new MenuItem({label: t('presence_updated', {
date: new Date(friend.presence.updatedAt * 1000),