From fa117bcbaa333befd027528d4cecd358a3d52587 Mon Sep 17 00:00:00 2001 From: zoro Date: Wed, 1 Jul 2026 19:02:00 -0700 Subject: [PATCH] profile: localize chart dates with Intl (#180) --- messages/cs-CZ.json | 9 ----- messages/de-DE.json | 9 ----- messages/en.json | 9 ----- messages/es-ES.json | 9 ----- messages/fr-FR.json | 9 ----- messages/it-IT.json | 9 ----- messages/ja-JP.json | 9 ----- messages/ko-KR.json | 9 ----- messages/nl-NL.json | 9 ----- messages/pl-PL.json | 9 ----- messages/pt-BR.json | 9 ----- messages/ru-RU.json | 9 ----- messages/zh-CN.json | 9 ----- messages/zh-TW.json | 9 ----- src/modules/player/chart/use-player-chart.ts | 39 +++++++++++--------- 15 files changed, 22 insertions(+), 143 deletions(-) diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index a486314..3138bc7 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -578,15 +578,6 @@ "chartTimeAll": "", "chartLastNDays": "", "chartNow": "", - "chartToday": "", - "chartYesterday": "", - "chartDaysAgo": "", - "chartDaysAgoShort": "", - "chartOneWeekAgo": "", - "chartTwoWeeksAgo": "", - "chartWeeksAgoShort": "", - "chartOneMonthAgo": "", - "chartMonthsAgoShort": "", "chartAvgPerDay": "", "chartEstimatedSuffix": "", "chartTooltipRank": "", diff --git a/messages/de-DE.json b/messages/de-DE.json index 5c54820..52306fe 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -578,15 +578,6 @@ "chartTimeAll": "Alle", "chartLastNDays": "Letzte {days} Tage", "chartNow": "Jetzt", - "chartToday": "Heute", - "chartYesterday": "Gestern", - "chartDaysAgo": "Vor {days} Tagen", - "chartDaysAgoShort": "", - "chartOneWeekAgo": "", - "chartTwoWeeksAgo": "", - "chartWeeksAgoShort": "", - "chartOneMonthAgo": "", - "chartMonthsAgoShort": "", "chartAvgPerDay": "", "chartEstimatedSuffix": "", "chartTooltipRank": "", diff --git a/messages/en.json b/messages/en.json index 43b8189..81c0917 100644 --- a/messages/en.json +++ b/messages/en.json @@ -579,15 +579,6 @@ "chartTimeAll": "All", "chartLastNDays": "Last {days} Days", "chartNow": "Now", - "chartToday": "Today", - "chartYesterday": "Yesterday", - "chartDaysAgo": "{days} days ago", - "chartDaysAgoShort": "{days}d ago", - "chartOneWeekAgo": "1 week ago", - "chartTwoWeeksAgo": "2 weeks ago", - "chartWeeksAgoShort": "{weeks}w ago", - "chartOneMonthAgo": "1 month ago", - "chartMonthsAgoShort": "{months}mo ago", "chartAvgPerDay": "Avg/Day", "chartEstimatedSuffix": " (estimated)", "chartTooltipRank": "Rank: #{rank}", diff --git a/messages/es-ES.json b/messages/es-ES.json index e960a8b..23fdedd 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -578,15 +578,6 @@ "chartTimeAll": "Todo", "chartLastNDays": "Últimos {days} Días", "chartNow": "Ahora", - "chartToday": "Hoy", - "chartYesterday": "Ayer", - "chartDaysAgo": "Hace {days} días", - "chartDaysAgoShort": "Hace {days}d", - "chartOneWeekAgo": "Hace 1 semana", - "chartTwoWeeksAgo": "Hace 2 semanas", - "chartWeeksAgoShort": "Hace {weeks} sem.", - "chartOneMonthAgo": "Hace 1 mes", - "chartMonthsAgoShort": "Hace {months} meses", "chartAvgPerDay": "Media diaria", "chartEstimatedSuffix": " (estimado)", "chartTooltipRank": "Rango: #{rank}", diff --git a/messages/fr-FR.json b/messages/fr-FR.json index b958ffe..4b2cf37 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -578,15 +578,6 @@ "chartTimeAll": "Tout", "chartLastNDays": "{days} derniers jours", "chartNow": "Actuel", - "chartToday": "Aujourd'hui", - "chartYesterday": "Hier", - "chartDaysAgo": "Il y a {days} jours", - "chartDaysAgoShort": "Il y a {days}j", - "chartOneWeekAgo": "Il y a 1 semaine", - "chartTwoWeeksAgo": "Il y a 2 semaines", - "chartWeeksAgoShort": "Il y a {weeks}sem", - "chartOneMonthAgo": "Il y a 1 mois", - "chartMonthsAgoShort": "Il y a {months}mo", "chartAvgPerDay": "Moy./Jour", "chartEstimatedSuffix": " (estimé)", "chartTooltipRank": "Classement : #{rank}", diff --git a/messages/it-IT.json b/messages/it-IT.json index 6b41357..f0bccb6 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -578,15 +578,6 @@ "chartTimeAll": "Tutto", "chartLastNDays": "Ultimi {days} giorni", "chartNow": "Ora", - "chartToday": "Oggi", - "chartYesterday": "Ieri", - "chartDaysAgo": "{days} {days, plural, one {giorno} other {giorni}} fa", - "chartDaysAgoShort": "{days}gg fa", - "chartOneWeekAgo": "Una settimana fa", - "chartTwoWeeksAgo": "2 settimane fa", - "chartWeeksAgoShort": "{weeks}sett. fa", - "chartOneMonthAgo": "Un mese fa", - "chartMonthsAgoShort": "{months}m fa", "chartAvgPerDay": "Media/Giorno", "chartEstimatedSuffix": " (stimato)", "chartTooltipRank": "Posizione: #{rank}", diff --git a/messages/ja-JP.json b/messages/ja-JP.json index e38a034..be4cf98 100644 --- a/messages/ja-JP.json +++ b/messages/ja-JP.json @@ -578,15 +578,6 @@ "chartTimeAll": "全て", "chartLastNDays": "過去{days}日", "chartNow": "現在", - "chartToday": "今日", - "chartYesterday": "昨日", - "chartDaysAgo": "{days} 日前", - "chartDaysAgoShort": "{days} 日前", - "chartOneWeekAgo": "1週間前", - "chartTwoWeeksAgo": "2週間前", - "chartWeeksAgoShort": "{weeks} 週間前", - "chartOneMonthAgo": "1ヶ月前", - "chartMonthsAgoShort": "{months} ヶ月前", "chartAvgPerDay": "平均/日", "chartEstimatedSuffix": "(推定)", "chartTooltipRank": "ランク: #{rank}", diff --git a/messages/ko-KR.json b/messages/ko-KR.json index a675b84..dccff3d 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -578,15 +578,6 @@ "chartTimeAll": "전체", "chartLastNDays": "최근 {days}일", "chartNow": "지금", - "chartToday": "오늘", - "chartYesterday": "어제", - "chartDaysAgo": "{days}일 전", - "chartDaysAgoShort": "{days}일 전", - "chartOneWeekAgo": "1주 전", - "chartTwoWeeksAgo": "2주 전", - "chartWeeksAgoShort": "{weeks}주 전", - "chartOneMonthAgo": "1개월 전", - "chartMonthsAgoShort": "{months}개월 전", "chartAvgPerDay": "일평균", "chartEstimatedSuffix": " (추정)", "chartTooltipRank": "순위: #{rank}", diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 9cc1307..318b966 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -578,15 +578,6 @@ "chartTimeAll": "Alles", "chartLastNDays": "Laatste {days} dagen", "chartNow": "Nu", - "chartToday": "Vandaag", - "chartYesterday": "Gisteren", - "chartDaysAgo": "{days} dagen geleden", - "chartDaysAgoShort": "{days}d geleden", - "chartOneWeekAgo": "1 week geleden", - "chartTwoWeeksAgo": "2 weken geleden", - "chartWeeksAgoShort": "{weeks}w geleden", - "chartOneMonthAgo": "1 maand geleden", - "chartMonthsAgoShort": "{months}mnd geleden", "chartAvgPerDay": "Gem./dag", "chartEstimatedSuffix": " (geschat)", "chartTooltipRank": "Rang: #{rank}", diff --git a/messages/pl-PL.json b/messages/pl-PL.json index 3adb619..d9e5521 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -578,15 +578,6 @@ "chartTimeAll": "Pełny zakres", "chartLastNDays": "Ostatnie {days} dni", "chartNow": "Teraz", - "chartToday": "Dzisiaj", - "chartYesterday": "Wczoraj", - "chartDaysAgo": "{days} dni temu", - "chartDaysAgoShort": "{days} dni temu", - "chartOneWeekAgo": "Tydzień temu", - "chartTwoWeeksAgo": "2 tygodnie temu", - "chartWeeksAgoShort": "{weeks} tyg. temu", - "chartOneMonthAgo": "Miesiąc temu", - "chartMonthsAgoShort": "{months} mies. temu", "chartAvgPerDay": "Śr./dzień", "chartEstimatedSuffix": " (szacowane)", "chartTooltipRank": "Ranga: #{rank}", diff --git a/messages/pt-BR.json b/messages/pt-BR.json index ecc6b8b..345e780 100644 --- a/messages/pt-BR.json +++ b/messages/pt-BR.json @@ -578,15 +578,6 @@ "chartTimeAll": "Todos", "chartLastNDays": "Últimos dias de {days}", "chartNow": "Agora", - "chartToday": "Hoje", - "chartYesterday": "Ontem", - "chartDaysAgo": "{days} dias atrás", - "chartDaysAgoShort": "há {days}d", - "chartOneWeekAgo": "1 semana atrás", - "chartTwoWeeksAgo": "2 semanas atrás", - "chartWeeksAgoShort": "{weeks}s ago", - "chartOneMonthAgo": "1 mês atrás", - "chartMonthsAgoShort": "{months}meses atrás", "chartAvgPerDay": "Média/dia", "chartEstimatedSuffix": "(estimado)", "chartTooltipRank": "Classificação: #{rank}", diff --git a/messages/ru-RU.json b/messages/ru-RU.json index c142ea2..a1002e3 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -578,15 +578,6 @@ "chartTimeAll": "Все", "chartLastNDays": "За последние {days} дней", "chartNow": "Только что", - "chartToday": "Сегодня", - "chartYesterday": "Вчера", - "chartDaysAgo": "{days} дней назад", - "chartDaysAgoShort": "{days}дн. назад", - "chartOneWeekAgo": "Неделю назад", - "chartTwoWeeksAgo": "2 недели назад", - "chartWeeksAgoShort": "{weeks}н. назад", - "chartOneMonthAgo": "Месяц назад", - "chartMonthsAgoShort": "{months}мес. назад", "chartAvgPerDay": "Средн./в день", "chartEstimatedSuffix": " (примерно)", "chartTooltipRank": "Место: #{rank}", diff --git a/messages/zh-CN.json b/messages/zh-CN.json index 0939e68..43dc3ae 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -578,15 +578,6 @@ "chartTimeAll": "全部", "chartLastNDays": "前 {days} 天", "chartNow": "现在", - "chartToday": "今天", - "chartYesterday": "昨天", - "chartDaysAgo": "{days}天前", - "chartDaysAgoShort": "{days}天前", - "chartOneWeekAgo": "一星期前", - "chartTwoWeeksAgo": "两星期前", - "chartWeeksAgoShort": "{weeks}星期前", - "chartOneMonthAgo": "一个月前", - "chartMonthsAgoShort": "{months}个月前", "chartAvgPerDay": "平均变化/天", "chartEstimatedSuffix": " (估计值)", "chartTooltipRank": "排名: #{rank}", diff --git a/messages/zh-TW.json b/messages/zh-TW.json index f02c9c0..6e3fd3a 100644 --- a/messages/zh-TW.json +++ b/messages/zh-TW.json @@ -578,15 +578,6 @@ "chartTimeAll": "", "chartLastNDays": "", "chartNow": "", - "chartToday": "", - "chartYesterday": "", - "chartDaysAgo": "", - "chartDaysAgoShort": "", - "chartOneWeekAgo": "", - "chartTwoWeeksAgo": "", - "chartWeeksAgoShort": "", - "chartOneMonthAgo": "", - "chartMonthsAgoShort": "", "chartAvgPerDay": "", "chartEstimatedSuffix": "", "chartTooltipRank": "", diff --git a/src/modules/player/chart/use-player-chart.ts b/src/modules/player/chart/use-player-chart.ts index 4901a2a..60d47fe 100644 --- a/src/modules/player/chart/use-player-chart.ts +++ b/src/modules/player/chart/use-player-chart.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore import { Result } from 'better-result'; import type { Chart } from 'chart.js'; -import { useTranslations } from 'use-intl'; +import { useLocale, useTranslations } from 'use-intl'; import { z } from 'zod'; import type { MetricKey, MetricLabel, PlayerChartStats, TimeRange } from '@/modules/player/chart/chart-types'; @@ -65,27 +65,30 @@ function getIsMobile() { return mobileQuery?.matches ?? false; } -type Translator = ReturnType>; - -function formatDaysAgo(t: Translator, daysAgo: number, short: boolean) { - if (daysAgo === 0) return t('chartToday'); +function formatDaysAgo(formatters: ChartDateFormatters, daysAgo: number, short: boolean) { + if (daysAgo < 7) return formatters[short ? 'short' : 'long'].format(-daysAgo, 'day'); if (short) { - if (daysAgo < 7) return t('chartDaysAgoShort', { days: daysAgo }); - if (daysAgo < 30) return t('chartWeeksAgoShort', { weeks: Math.round(daysAgo / 7) }); - return t('chartMonthsAgoShort', { months: Math.round(daysAgo / 30) }); + if (daysAgo < 30) return formatters.short.format(-Math.round(daysAgo / 7), 'week'); + return formatters.short.format(-Math.round(daysAgo / 30), 'month'); } - if (daysAgo === 1) return t('chartYesterday'); - if (daysAgo === 7) return t('chartOneWeekAgo'); - if (daysAgo === 14) return t('chartTwoWeeksAgo'); - if (daysAgo === 30) return t('chartOneMonthAgo'); - return t('chartDaysAgo', { days: daysAgo }); + return formatters.long.format(-daysAgo, 'day'); } function getDaysAgo(date: Date, now: Date) { return Math.max(0, Math.round((now.getTime() - date.getTime()) / DAY_MS)); } +type ChartDateFormatters = ReturnType; + +function createChartDateFormatters(locale: string) { + return { + long: new Intl.RelativeTimeFormat(locale, { numeric: 'auto', style: 'long' }), + short: new Intl.RelativeTimeFormat(locale, { numeric: 'auto', style: 'narrow' }) + }; +} + export function usePlayerChart(playerId: string, stats: PlayerChartStats, history: PlayerControllerGetPlayerHistoryItem[]) { + const locale = useLocale(); const t = useTranslations('player'); const tc = useTranslations('common'); const initialPrefs = useRef(loadPrefs()).current; @@ -189,6 +192,7 @@ export function usePlayerChart(playerId: string, stats: PlayerChartStats, histor const visibleDays = useMemo(() => getVisibleChartDayCount(sortedHistory, now), [sortedHistory, now]); const minTime = sortedHistory.length > 0 ? new Date(sortedHistory[0].createdAt).getTime() : nowTime; const maxTime = nowTime; + const chartDateFormatters = useMemo(() => createChartDateFormatters(locale), [locale]); const chartPadding = useMemo(() => getPlayerChartPadding(sortedHistory.length + 1), [sortedHistory.length]); @@ -253,7 +257,8 @@ export function usePlayerChart(playerId: string, stats: PlayerChartStats, histor ]); const scales = useMemo(() => { - const formatTick = (value: number) => (value === maxTime ? t('chartNow') : formatDaysAgo(t, getDaysAgo(new Date(value), now), isMobile)); + const formatTick = (value: number) => + value === maxTime ? t('chartNow') : formatDaysAgo(chartDateFormatters, getDaysAgo(new Date(value), now), isMobile); return buildPlayerChartScales({ activeKeys, chartColors, @@ -265,7 +270,7 @@ export function usePlayerChart(playerId: string, stats: PlayerChartStats, histor maxTime, formatTick }); - }, [activeKeys, chartColors, isSingle, isMobile, t, metricLabels, visibleDays, minTime, maxTime, now]); + }, [activeKeys, chartColors, isSingle, isMobile, t, metricLabels, visibleDays, minTime, maxTime, now, chartDateFormatters]); const avgPerDayLabel = t('chartAvgPerDay'); @@ -293,7 +298,7 @@ export function usePlayerChart(playerId: string, stats: PlayerChartStats, histor const x = context[0]?.parsed.x; if (x == null) return ''; if (x === nowTime) return t('chartNow'); - return formatDaysAgo(t, getDaysAgo(new Date(x), now), false); + return formatDaysAgo(chartDateFormatters, getDaysAgo(new Date(x), now), false); }, label: (context: { datasetIndex: number; dataIndex: number; parsed: { y: number | null } }) => { const key = activeKeys[context.datasetIndex]; @@ -309,7 +314,7 @@ export function usePlayerChart(playerId: string, stats: PlayerChartStats, histor return { borderColor: color, backgroundColor: color }; } }), - [activeKeys, chartPadding, estimatedFlags, chartColors.metricBorder, formatTooltipValue, t, now, nowTime] + [activeKeys, chartPadding, estimatedFlags, chartColors.metricBorder, formatTooltipValue, t, now, nowTime, chartDateFormatters] ); return {