Files
iptvnator/libs/ui/components/src/lib/external-playback-dock/external-playback-dock.component.html
T
4grayandClaude Opus 5.5 6e18983400 fix(i18n): translate hard-coded labels, snackbars and missing keys (#1867)
* fix(i18n): translate hard-coded labels, snackbars and missing keys

Catalog screens, snackbars and external player messages showed English
in every locale, and seven keys used in code were missing from en.json,
so ngx-translate rendered them raw.

- Catalog: "All items", item and channel counts, channel sort menus and
  tooltips, unnamed-category and empty-category labels, LIVE/PAUSED
  badges and the Xtream global search summary are translated. The Xtream
  and Stalker stores no longer bake an English name into the every-item
  sentinel; the facades return a null title and the view translates it.
- Snackbars: Xtream/Stalker request failures, the 413 upload error,
  backup export/import results and the category visibility failure use
  keys; every "Close" action uses CLOSE.
- External player: the main process sends an error code instead of an
  English sentence (player-error event, session errorCode, and a tag in
  rejected launch errors); the renderer, dock and VOD primary button
  translate it. The external player info dialog is translated.
- Adds the seven missing keys and 43 new ones, translated in all 18
  locales; seven legitimately identical values are baselined.
- tools/i18n/check-usage.mjs fails on keys used in code but missing from
  en.json; it runs in i18n:check (and so in CI through i18n:validate).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(i18n): translate catalog counts in the template and skip inline template comments

- The category subtitle hands a key to the translate pipe instead of
  caching translate.instant(), so a cold start re-renders it once the
  language file loads.
- The Xtream live root count uses the singular/plural item keys, so one
  result no longer reads "1 channels".
- check-usage.mjs strips HTML comments inside inline templates of
  TypeScript files, so a commented-out key no longer fails the check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(i18n): check keys in parenthesised translate pipe operands

`(expanded() ? 'SHOW_LESS' : 'SHOW_MORE') | translate` yields keys that
neither precede the pipe directly nor contain a dot, so the usage check
missed them. It now reads the ternary and fallback branches of a
parenthesised operand; a literal compared in the condition is not read
as a key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(e2e): expect the translated external player failure in the dock

A launch failure without an error code now shows the translated generic
status in the playback dock, with the raw main-process detail as its
tooltip. The ClearKey DASH flow asserted the raw English text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(playback): keep the IPC error tag out of the stored session detail

A tagged launch failure reached ExternalPlayerSession.error unchanged, so
the dock tooltip showed "[iptvnator:external-player:start-failed]". The
registry now strips the tag when it stores the detail; the rejected IPC
error keeps it for the renderer to read the code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(web): expect translated portal request failure toasts

#1861's new resolved-failure specs asserted the English toast text; the
toasts now go through PORTALS.REQUEST_ERRORS keys, so the specs check
the key and its message/status params.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(i18n): read only returned branches of a grouped translate operand

A literal at the start of a condition, as in
`('ERROR' === status() ? 'CLOSE' : 'CLOSE') | translate`, was taken for
a translation key, so a valid template could fail the usage check. A
grouped literal now counts only when it ends its operand (end of group,
`:`, `||` or `??`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:02:21 +02:00

125 lines
4.3 KiB
HTML

<section
class="external-playback-dock"
[class.external-playback-dock--compact]="compact()"
[class.external-playback-dock--error]="session().status === 'error'"
[attr.aria-busy]="session().status === 'launching' ? 'true' : null"
>
<button
type="button"
class="external-playback-dock__artwork"
[class.external-playback-dock__artwork--interactive]="
artworkInteractive()
"
[disabled]="!artworkInteractive()"
[attr.aria-label]="
artworkInteractive()
? ('WORKSPACE.SHELL.EXTERNAL_PLAYBACK_OPEN_IN_PLAYLIST'
| translate)
: null
"
(click)="onArtworkClick()"
>
@if (showArtwork()) {
<img
[src]="artworkUrl()"
[alt]="session().title"
loading="lazy"
(error)="onArtworkError()"
/>
} @else {
<div class="external-playback-dock__placeholder">
<mat-icon>{{ artworkPlaceholderIcon() }}</mat-icon>
</div>
}
</button>
<div class="external-playback-dock__meta">
<div class="external-playback-dock__title">{{ displayTitle() }}</div>
<div class="external-playback-dock__status" aria-live="polite">
<span class="external-playback-dock__chip">{{
playerLabel()
}}</span>
<span
class="external-playback-dock__status-separator"
aria-hidden="true"
>·</span
>
@if (showSpinner()) {
<mat-spinner
aria-hidden="true"
diameter="12"
strokeWidth="2"
></mat-spinner>
} @else if (showStatusIcon()) {
<mat-icon
aria-hidden="true"
class="external-playback-dock__status-icon"
>{{ statusIcon() }}</mat-icon
>
}
<span
class="external-playback-dock__status-text"
[attr.title]="
session().status === 'error' ? session().error : null
"
>
{{ statusLabelKey() | translate: { player: playerLabel() } }}
</span>
</div>
</div>
<div class="external-playback-dock__actions">
@if (showCloseAction()) {
<button
type="button"
mat-button
class="external-playback-dock__button"
[attr.aria-label]="
'WORKSPACE.SHELL.EXTERNAL_PLAYBACK_CLOSE' | translate
"
(click)="onCloseClick()"
>
<mat-icon aria-hidden="true">stop_circle</mat-icon>
@if (!compact()) {
<span>{{
'WORKSPACE.SHELL.EXTERNAL_PLAYBACK_CLOSE' | translate
}}</span>
}
</button>
}
@if (showDismissAction()) {
@if (compact()) {
<button
type="button"
mat-icon-button
class="external-playback-dock__button external-playback-dock__dismiss"
[attr.aria-label]="
'WORKSPACE.SHELL.EXTERNAL_PLAYBACK_DISMISS' | translate
"
(click)="onDismissClick()"
>
<mat-icon aria-hidden="true">close</mat-icon>
</button>
} @else {
<button
type="button"
mat-button
class="external-playback-dock__button external-playback-dock__dismiss"
[attr.aria-label]="
'WORKSPACE.SHELL.EXTERNAL_PLAYBACK_DISMISS' | translate
"
(click)="onDismissClick()"
>
<mat-icon aria-hidden="true">close</mat-icon>
@if (!compact()) {
<span>{{
'WORKSPACE.SHELL.EXTERNAL_PLAYBACK_DISMISS'
| translate
}}</span>
}
</button>
}
}
</div>
</section>