From 5cfe3aef06c65bcb54395838ca2f115e61276287 Mon Sep 17 00:00:00 2001 From: 4gray Date: Fri, 22 May 2026 01:02:04 +0300 Subject: [PATCH] fix(dashboard): localize progress labels --- apps/web/src/assets/i18n/ar.json | 5 +++ apps/web/src/assets/i18n/ary.json | 5 +++ apps/web/src/assets/i18n/by.json | 5 +++ apps/web/src/assets/i18n/de.json | 5 +++ apps/web/src/assets/i18n/el.json | 5 +++ apps/web/src/assets/i18n/en.json | 5 +++ apps/web/src/assets/i18n/es.json | 5 +++ apps/web/src/assets/i18n/fr.json | 5 +++ apps/web/src/assets/i18n/it.json | 5 +++ apps/web/src/assets/i18n/ja.json | 5 +++ apps/web/src/assets/i18n/ko.json | 5 +++ apps/web/src/assets/i18n/nl.json | 5 +++ apps/web/src/assets/i18n/pl.json | 5 +++ apps/web/src/assets/i18n/pt.json | 5 +++ apps/web/src/assets/i18n/ru.json | 5 +++ apps/web/src/assets/i18n/tr.json | 5 +++ apps/web/src/assets/i18n/zh.json | 5 +++ apps/web/src/assets/i18n/zhtw.json | 5 +++ .../workspace-dashboard-rails.component.html | 10 ++++- ...orkspace-dashboard-rails.component.spec.ts | 29 ++++++++++--- .../workspace-dashboard-rails.component.ts | 43 ++++++++++++++----- 21 files changed, 152 insertions(+), 20 deletions(-) diff --git a/apps/web/src/assets/i18n/ar.json b/apps/web/src/assets/i18n/ar.json index 4ecacc5a4..76e26f667 100644 --- a/apps/web/src/assets/i18n/ar.json +++ b/apps/web/src/assets/i18n/ar.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "متابعة المشاهدة", "LIVE_FAVORITES": "يُبث الآن على المفضلة لديك", "LIVE_RECENT": "متابعة البث المباشر", + "REMAINING_SECONDS": "متبقٍ {{seconds}} ث", + "REMAINING_MINUTES": "متبقٍ {{minutes}} د", + "REMAINING_HOURS": "متبقٍ {{hours}} س", + "REMAINING_HOURS_MINUTES": "متبقٍ {{hours}} س {{minutes}} د", + "PERCENT_WATCHED": "تمت مشاهدة {{value}}%", "UNTITLED_SOURCE": "مصدر بدون عنوان", "GLOBAL_FAVORITES": "المفضلة العامة", "REMOVE_FROM_HISTORY": "إزالة من السجل", diff --git a/apps/web/src/assets/i18n/ary.json b/apps/web/src/assets/i18n/ary.json index facc7890c..fedc92b2c 100644 --- a/apps/web/src/assets/i18n/ary.json +++ b/apps/web/src/assets/i18n/ary.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "كمل المشاهدة", "LIVE_FAVORITES": "مباشر دابا على المفضلة ديالك", "LIVE_RECENT": "كمل مع المباشر", + "REMAINING_SECONDS": "باقي {{seconds}} ث", + "REMAINING_MINUTES": "باقي {{minutes}} د", + "REMAINING_HOURS": "باقي {{hours}} س", + "REMAINING_HOURS_MINUTES": "باقي {{hours}} س {{minutes}} د", + "PERCENT_WATCHED": "تشاهد {{value}}%", "UNTITLED_SOURCE": "مصدر بلا عنوان", "GLOBAL_FAVORITES": "المفضلة العامة", "REMOVE_FROM_HISTORY": "حيد من السجل", diff --git a/apps/web/src/assets/i18n/by.json b/apps/web/src/assets/i18n/by.json index 4654352c0..e10779332 100644 --- a/apps/web/src/assets/i18n/by.json +++ b/apps/web/src/assets/i18n/by.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "Працягнуць прагляд", "LIVE_FAVORITES": "Цяпер у эфіры на выбраных", "LIVE_RECENT": "Працягнуць прагляд эфіру", + "REMAINING_SECONDS": "Засталося {{seconds}} с", + "REMAINING_MINUTES": "Засталося {{minutes}} хв", + "REMAINING_HOURS": "Засталося {{hours}} г", + "REMAINING_HOURS_MINUTES": "Засталося {{hours}} г {{minutes}} хв", + "PERCENT_WATCHED": "Прагледжана {{value}}%", "UNTITLED_SOURCE": "Крыніца без назвы", "GLOBAL_FAVORITES": "Усе фаварыты", "REMOVE_FROM_HISTORY": "Выдаліць з гісторыі", diff --git a/apps/web/src/assets/i18n/de.json b/apps/web/src/assets/i18n/de.json index aa51b7a9a..4de9bb902 100644 --- a/apps/web/src/assets/i18n/de.json +++ b/apps/web/src/assets/i18n/de.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "Weiter schauen", "LIVE_FAVORITES": "Jetzt live auf Ihren Favoriten", "LIVE_RECENT": "Mit Live-TV weiterschauen", + "REMAINING_SECONDS": "Noch {{seconds}} s", + "REMAINING_MINUTES": "Noch {{minutes}} Min.", + "REMAINING_HOURS": "Noch {{hours}} Std.", + "REMAINING_HOURS_MINUTES": "Noch {{hours}} Std. {{minutes}} Min.", + "PERCENT_WATCHED": "{{value}}% angesehen", "UNTITLED_SOURCE": "Unbenannte Quelle", "GLOBAL_FAVORITES": "Globale Favoriten", "REMOVE_FROM_HISTORY": "Aus Verlauf entfernen", diff --git a/apps/web/src/assets/i18n/el.json b/apps/web/src/assets/i18n/el.json index 53ced9c68..02df9c93a 100644 --- a/apps/web/src/assets/i18n/el.json +++ b/apps/web/src/assets/i18n/el.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "Συνέχιση παρακολούθησης", "LIVE_FAVORITES": "Ζωντανά τώρα στα αγαπημένα σας", "LIVE_RECENT": "Συνέχιση με ζωντανή TV", + "REMAINING_SECONDS": "Απομένουν {{seconds}} δ", + "REMAINING_MINUTES": "Απομένουν {{minutes}} λ", + "REMAINING_HOURS": "Απομένουν {{hours}} ώ", + "REMAINING_HOURS_MINUTES": "Απομένουν {{hours}} ώ {{minutes}} λ", + "PERCENT_WATCHED": "Προβλήθηκε {{value}}%", "UNTITLED_SOURCE": "Πηγή χωρίς τίτλο", "GLOBAL_FAVORITES": "Βασικά Αγαπημένα", "REMOVE_FROM_HISTORY": "Αφαίρεση από το ιστορικό", diff --git a/apps/web/src/assets/i18n/en.json b/apps/web/src/assets/i18n/en.json index efde90ae8..3f1ab3fb5 100644 --- a/apps/web/src/assets/i18n/en.json +++ b/apps/web/src/assets/i18n/en.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "Continue Watching", "LIVE_FAVORITES": "Live now on your favorites", "LIVE_RECENT": "Continue with live TV", + "REMAINING_SECONDS": "{{seconds}}s left", + "REMAINING_MINUTES": "{{minutes}}m left", + "REMAINING_HOURS": "{{hours}}h left", + "REMAINING_HOURS_MINUTES": "{{hours}}h {{minutes}}m left", + "PERCENT_WATCHED": "{{value}}% watched", "UNTITLED_SOURCE": "Untitled source", "GLOBAL_FAVORITES": "Global Favorites", "REMOVE_FROM_HISTORY": "Remove from history", diff --git a/apps/web/src/assets/i18n/es.json b/apps/web/src/assets/i18n/es.json index 637be0594..915a13d36 100644 --- a/apps/web/src/assets/i18n/es.json +++ b/apps/web/src/assets/i18n/es.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "Continuar viendo", "LIVE_FAVORITES": "Ahora en vivo en tus favoritos", "LIVE_RECENT": "Continuar con TV en vivo", + "REMAINING_SECONDS": "Quedan {{seconds}} s", + "REMAINING_MINUTES": "Quedan {{minutes}} min", + "REMAINING_HOURS": "Quedan {{hours}} h", + "REMAINING_HOURS_MINUTES": "Quedan {{hours}} h {{minutes}} min", + "PERCENT_WATCHED": "{{value}}% visto", "UNTITLED_SOURCE": "Fuente sin título", "GLOBAL_FAVORITES": "Favoritos globales", "REMOVE_FROM_HISTORY": "Quitar del historial", diff --git a/apps/web/src/assets/i18n/fr.json b/apps/web/src/assets/i18n/fr.json index 161c3c106..d725f6ba9 100644 --- a/apps/web/src/assets/i18n/fr.json +++ b/apps/web/src/assets/i18n/fr.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "Reprendre la lecture", "LIVE_FAVORITES": "En direct sur vos favoris", "LIVE_RECENT": "Reprendre la TV en direct", + "REMAINING_SECONDS": "Il reste {{seconds}} s", + "REMAINING_MINUTES": "Il reste {{minutes}} min", + "REMAINING_HOURS": "Il reste {{hours}} h", + "REMAINING_HOURS_MINUTES": "Il reste {{hours}} h {{minutes}} min", + "PERCENT_WATCHED": "{{value}}% regardé", "UNTITLED_SOURCE": "Source sans titre", "GLOBAL_FAVORITES": "Favoris globaux", "REMOVE_FROM_HISTORY": "Retirer de l'historique", diff --git a/apps/web/src/assets/i18n/it.json b/apps/web/src/assets/i18n/it.json index d527787ab..8495b34cb 100644 --- a/apps/web/src/assets/i18n/it.json +++ b/apps/web/src/assets/i18n/it.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "Continua a guardare", "LIVE_FAVORITES": "Live ora sui tuoi preferiti", "LIVE_RECENT": "Continua con la TV live", + "REMAINING_SECONDS": "Mancano {{seconds}} s", + "REMAINING_MINUTES": "Mancano {{minutes}} min", + "REMAINING_HOURS": "Mancano {{hours}} h", + "REMAINING_HOURS_MINUTES": "Mancano {{hours}} h {{minutes}} min", + "PERCENT_WATCHED": "{{value}}% visto", "UNTITLED_SOURCE": "Sorgente senza titolo", "GLOBAL_FAVORITES": "Preferiti globali", "REMOVE_FROM_HISTORY": "Rimuovi dalla cronologia", diff --git a/apps/web/src/assets/i18n/ja.json b/apps/web/src/assets/i18n/ja.json index 44817f13f..d0ce7368a 100644 --- a/apps/web/src/assets/i18n/ja.json +++ b/apps/web/src/assets/i18n/ja.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "視聴を再開", "LIVE_FAVORITES": "お気に入りで現在放送中", "LIVE_RECENT": "ライブTVの視聴を再開", + "REMAINING_SECONDS": "残り{{seconds}}秒", + "REMAINING_MINUTES": "残り{{minutes}}分", + "REMAINING_HOURS": "残り{{hours}}時間", + "REMAINING_HOURS_MINUTES": "残り{{hours}}時間{{minutes}}分", + "PERCENT_WATCHED": "{{value}}% 視聴済み", "UNTITLED_SOURCE": "無題のソース", "GLOBAL_FAVORITES": "グローバルお気に入り", "REMOVE_FROM_HISTORY": "履歴から削除", diff --git a/apps/web/src/assets/i18n/ko.json b/apps/web/src/assets/i18n/ko.json index 3e5849dc4..bb72c1867 100644 --- a/apps/web/src/assets/i18n/ko.json +++ b/apps/web/src/assets/i18n/ko.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "이어서 시청", "LIVE_FAVORITES": "즐겨찾기에서 지금 라이브 중인 채널입니다", "LIVE_RECENT": "라이브 TV 이어서 시청", + "REMAINING_SECONDS": "{{seconds}}초 남음", + "REMAINING_MINUTES": "{{minutes}}분 남음", + "REMAINING_HOURS": "{{hours}}시간 남음", + "REMAINING_HOURS_MINUTES": "{{hours}}시간 {{minutes}}분 남음", + "PERCENT_WATCHED": "{{value}}% 시청함", "UNTITLED_SOURCE": "제목 없는 소스", "GLOBAL_FAVORITES": "전체 즐겨찾기", "REMOVE_FROM_HISTORY": "기록에서 제거", diff --git a/apps/web/src/assets/i18n/nl.json b/apps/web/src/assets/i18n/nl.json index f11ced48f..1130411fe 100644 --- a/apps/web/src/assets/i18n/nl.json +++ b/apps/web/src/assets/i18n/nl.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "Verder kijken", "LIVE_FAVORITES": "Nu live op je favorieten", "LIVE_RECENT": "Verder kijken met live tv", + "REMAINING_SECONDS": "Nog {{seconds}} sec", + "REMAINING_MINUTES": "Nog {{minutes}} min", + "REMAINING_HOURS": "Nog {{hours}} u", + "REMAINING_HOURS_MINUTES": "Nog {{hours}} u {{minutes}} min", + "PERCENT_WATCHED": "{{value}}% bekeken", "UNTITLED_SOURCE": "Naamloze bron", "GLOBAL_FAVORITES": "Algemene favorieten", "REMOVE_FROM_HISTORY": "Verwijderen uit geschiedenis", diff --git a/apps/web/src/assets/i18n/pl.json b/apps/web/src/assets/i18n/pl.json index 82a01f75d..e472e8d85 100644 --- a/apps/web/src/assets/i18n/pl.json +++ b/apps/web/src/assets/i18n/pl.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "Kontynuuj oglądanie", "LIVE_FAVORITES": "Na żywo w Twoich ulubionych", "LIVE_RECENT": "Kontynuuj oglądanie na żywo", + "REMAINING_SECONDS": "Pozostało {{seconds}} s", + "REMAINING_MINUTES": "Pozostało {{minutes}} min", + "REMAINING_HOURS": "Pozostało {{hours}} godz.", + "REMAINING_HOURS_MINUTES": "Pozostało {{hours}} godz. {{minutes}} min", + "PERCENT_WATCHED": "Obejrzano {{value}}%", "UNTITLED_SOURCE": "Źródło bez tytułu", "GLOBAL_FAVORITES": "Globalne ulubione", "REMOVE_FROM_HISTORY": "Usuń z historii", diff --git a/apps/web/src/assets/i18n/pt.json b/apps/web/src/assets/i18n/pt.json index 4715f2cc8..62fd8bd5f 100644 --- a/apps/web/src/assets/i18n/pt.json +++ b/apps/web/src/assets/i18n/pt.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "Continuar assistindo", "LIVE_FAVORITES": "Ao vivo agora nos seus favoritos", "LIVE_RECENT": "Continuar com TV ao vivo", + "REMAINING_SECONDS": "Restam {{seconds}} s", + "REMAINING_MINUTES": "Restam {{minutes}} min", + "REMAINING_HOURS": "Restam {{hours}} h", + "REMAINING_HOURS_MINUTES": "Restam {{hours}} h {{minutes}} min", + "PERCENT_WATCHED": "{{value}}% assistido", "UNTITLED_SOURCE": "Fonte sem título", "GLOBAL_FAVORITES": "Favoritos globais", "REMOVE_FROM_HISTORY": "Remover do histórico", diff --git a/apps/web/src/assets/i18n/ru.json b/apps/web/src/assets/i18n/ru.json index d51948a34..d601f5a73 100644 --- a/apps/web/src/assets/i18n/ru.json +++ b/apps/web/src/assets/i18n/ru.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "Продолжить просмотр", "LIVE_FAVORITES": "Сейчас в эфире на избранных каналах", "LIVE_RECENT": "Продолжить просмотр прямого эфира", + "REMAINING_SECONDS": "Осталось {{seconds}} с", + "REMAINING_MINUTES": "Осталось {{minutes}} мин", + "REMAINING_HOURS": "Осталось {{hours}} ч", + "REMAINING_HOURS_MINUTES": "Осталось {{hours}} ч {{minutes}} мин", + "PERCENT_WATCHED": "Просмотрено {{value}}%", "UNTITLED_SOURCE": "Источник без названия", "GLOBAL_FAVORITES": "Глобальные избранные", "REMOVE_FROM_HISTORY": "Удалить из истории", diff --git a/apps/web/src/assets/i18n/tr.json b/apps/web/src/assets/i18n/tr.json index aa1a0354a..8e14010d4 100644 --- a/apps/web/src/assets/i18n/tr.json +++ b/apps/web/src/assets/i18n/tr.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "İzlemeye Devam Et", "LIVE_FAVORITES": "Favorilerinizde şu an canlı", "LIVE_RECENT": "Canlı TV ile devam et", + "REMAINING_SECONDS": "{{seconds}} sn kaldı", + "REMAINING_MINUTES": "{{minutes}} dk kaldı", + "REMAINING_HOURS": "{{hours}} sa kaldı", + "REMAINING_HOURS_MINUTES": "{{hours}} sa {{minutes}} dk kaldı", + "PERCENT_WATCHED": "{{value}}% izlendi", "UNTITLED_SOURCE": "Adsız kaynak", "GLOBAL_FAVORITES": "Tüm Favoriler", "REMOVE_FROM_HISTORY": "Geçmişten kaldır", diff --git a/apps/web/src/assets/i18n/zh.json b/apps/web/src/assets/i18n/zh.json index 2d2f6c20b..0fdc1f1f5 100644 --- a/apps/web/src/assets/i18n/zh.json +++ b/apps/web/src/assets/i18n/zh.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "继续观看", "LIVE_FAVORITES": "收藏频道正在播放", "LIVE_RECENT": "继续观看直播", + "REMAINING_SECONDS": "剩余 {{seconds}} 秒", + "REMAINING_MINUTES": "剩余 {{minutes}} 分钟", + "REMAINING_HOURS": "剩余 {{hours}} 小时", + "REMAINING_HOURS_MINUTES": "剩余 {{hours}} 小时 {{minutes}} 分钟", + "PERCENT_WATCHED": "已观看 {{value}}%", "UNTITLED_SOURCE": "未命名源", "GLOBAL_FAVORITES": "全局收藏", "REMOVE_FROM_HISTORY": "从历史记录中移除", diff --git a/apps/web/src/assets/i18n/zhtw.json b/apps/web/src/assets/i18n/zhtw.json index ae4e893a1..ca6c6a89d 100644 --- a/apps/web/src/assets/i18n/zhtw.json +++ b/apps/web/src/assets/i18n/zhtw.json @@ -853,6 +853,11 @@ "CONTINUE_WATCHING": "繼續觀看", "LIVE_FAVORITES": "我的最愛正在直播", "LIVE_RECENT": "繼續觀看直播電視", + "REMAINING_SECONDS": "剩餘 {{seconds}} 秒", + "REMAINING_MINUTES": "剩餘 {{minutes}} 分鐘", + "REMAINING_HOURS": "剩餘 {{hours}} 小時", + "REMAINING_HOURS_MINUTES": "剩餘 {{hours}} 小時 {{minutes}} 分鐘", + "PERCENT_WATCHED": "已觀看 {{value}}%", "UNTITLED_SOURCE": "未命名的來源", "GLOBAL_FAVORITES": "全域我的最愛", "REMOVE_FROM_HISTORY": "從歷史記錄中移除", diff --git a/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.html b/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.html index 86d9db6bf..1ed8e2679 100644 --- a/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.html +++ b/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.html @@ -85,12 +85,18 @@ @if (item.remainingLabel) { - {{ item.remainingLabel }} + {{ + item.remainingLabel.key + | translate: item.remainingLabel.params + }} · } - {{ item.watchProgress }}% watched + {{ + 'WORKSPACE.DASHBOARD.PERCENT_WATCHED' + | translate: { value: item.watchProgress } + }} } diff --git a/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.spec.ts b/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.spec.ts index befac9136..66d2dc2fa 100644 --- a/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.spec.ts +++ b/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.spec.ts @@ -349,40 +349,55 @@ describe('playback-position helpers', () => { ).toBeNull(); }); - it('formats sub-minute, minute, and hour-spanning remainders', () => { + it('returns translation keys and params for sub-minute, minute, and hour-spanning remainders', () => { expect( formatRemainingLabel({ positionSeconds: 5970, durationSeconds: 6000, }) - ).toBe('30s left'); + ).toEqual({ + key: 'WORKSPACE.DASHBOARD.REMAINING_SECONDS', + params: { seconds: 30 }, + }); expect( formatRemainingLabel({ positionSeconds: 0, durationSeconds: 1800, }) - ).toBe('30m left'); + ).toEqual({ + key: 'WORKSPACE.DASHBOARD.REMAINING_MINUTES', + params: { minutes: 30 }, + }); expect( formatRemainingLabel({ positionSeconds: 0, durationSeconds: 3600, }) - ).toBe('1h left'); + ).toEqual({ + key: 'WORKSPACE.DASHBOARD.REMAINING_HOURS', + params: { hours: 1 }, + }); expect( formatRemainingLabel({ positionSeconds: 600, durationSeconds: 6840, }) - ).toBe('1h 44m left'); + ).toEqual({ + key: 'WORKSPACE.DASHBOARD.REMAINING_HOURS_MINUTES', + params: { hours: 1, minutes: 44 }, + }); }); - it('clamps below zero — a completed program reads as "0s left" not negative', () => { + it('clamps below zero to the translated zero-seconds state', () => { expect( formatRemainingLabel({ positionSeconds: 7000, durationSeconds: 6000, }) - ).toBe('0s left'); + ).toEqual({ + key: 'WORKSPACE.DASHBOARD.REMAINING_SECONDS', + params: { seconds: 0 }, + }); }); }); }); diff --git a/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.ts b/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.ts index be70d17be..2165d4a8d 100644 --- a/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.ts +++ b/libs/workspace/dashboard/feature/src/lib/rails/workspace-dashboard-rails.component.ts @@ -76,8 +76,12 @@ interface DashboardHeroModel { readonly title: string; /** 0–100 watched, when a resume position is known. */ readonly watchProgress?: number | null; - /** "1h 04m left" — pre-formatted for direct rendering. */ - readonly remainingLabel?: string | null; + readonly remainingLabel?: DashboardRemainingLabel | null; +} + +export interface DashboardRemainingLabel { + readonly key: string; + readonly params: Record; } export type DashboardHeroBackdropSource = 'backdrop' | 'poster' | 'fallback'; @@ -334,7 +338,7 @@ export function playbackProgressPercent( export function formatRemainingLabel( position: { positionSeconds: number; durationSeconds?: number } | null -): string | null { +): DashboardRemainingLabel | null { if ( !position || position.durationSeconds == null || @@ -347,18 +351,30 @@ export function formatRemainingLabel( Math.round(position.durationSeconds - position.positionSeconds) ); if (remaining < 60) { - return `${remaining}s left`; + return { + key: 'WORKSPACE.DASHBOARD.REMAINING_SECONDS', + params: { seconds: remaining }, + }; } const totalMinutes = Math.round(remaining / 60); if (totalMinutes < 60) { - return `${totalMinutes}m left`; + return { + key: 'WORKSPACE.DASHBOARD.REMAINING_MINUTES', + params: { minutes: totalMinutes }, + }; } const hours = Math.floor(totalMinutes / 60); const minutes = totalMinutes % 60; if (minutes === 0) { - return `${hours}h left`; + return { + key: 'WORKSPACE.DASHBOARD.REMAINING_HOURS', + params: { hours }, + }; } - return `${hours}h ${minutes.toString().padStart(2, '0')}m left`; + return { + key: 'WORKSPACE.DASHBOARD.REMAINING_HOURS_MINUTES', + params: { hours, minutes }, + }; } function isXtreamAccountPlaylist( @@ -401,6 +417,10 @@ export class WorkspaceDashboardRailsComponent { private readonly snackBar = inject(MatSnackBar); private readonly store = inject(Store); private readonly translate = inject(TranslateService); + private readonly languageTick = toSignal( + this.translate.onLangChange.pipe(startWith(null)), + { initialValue: null } + ); private readonly shellActions = inject(WORKSPACE_SHELL_ACTIONS); private readonly epgService = inject(EpgService); @@ -447,12 +467,13 @@ export class WorkspaceDashboardRailsComponent { // (movies/series) get one rail; live channels get their own. Two card // formats — one rail each — so users can scan each surface at a glance // instead of decoding a mixed grid of fallback posters and channel logos. - readonly continueWatchingCards = computed(() => - this.data + readonly continueWatchingCards = computed(() => { + this.languageTick(); + return this.data .globalRecentVodItems() .slice(0, RAIL_ITEM_LIMIT) - .map((item) => this.toRecentCard(item)) - ); + .map((item) => this.toRecentCard(item)); + }); // Live rail source — favorited live channels take precedence so the EPG // enrichment lands on the channels the user actually cares about. When