mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 21:56:18 -08:00
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>
This commit is contained in:
1 parent
98784815b1
commit
6e18983400
106 files changed
+2695
-360
No files matched your search
+2
-2
@@ -29,7 +29,7 @@
|
||||
{{
|
||||
item.category_name ||
|
||||
$any(item).name ||
|
||||
'No category name'
|
||||
('PORTALS.UNNAMED_CATEGORY' | translate)
|
||||
}}
|
||||
</span>
|
||||
@if (showCounts()) {
|
||||
@@ -63,7 +63,7 @@
|
||||
} @else {
|
||||
<div class="category-empty-state">
|
||||
<app-workspace-context-error-view
|
||||
[title]="'No categories available'"
|
||||
[title]="'PORTALS.NO_CATEGORIES' | translate"
|
||||
[showIllustration]="false"
|
||||
[showActionButtons]="false"
|
||||
[viewType]="'EMPTY_CATEGORY'"
|
||||
|
||||
+2
-5
@@ -5,13 +5,10 @@
|
||||
>
|
||||
@if (showIllustration()) {
|
||||
@if (viewType() === 'ERROR') {
|
||||
<img
|
||||
alt="Something went wrong"
|
||||
src="./assets/images/broken-tv.svg"
|
||||
/>
|
||||
<img alt="" src="./assets/images/broken-tv.svg" />
|
||||
} @else if (viewType() === 'EMPTY_CATEGORY') {
|
||||
<img
|
||||
alt="empty category"
|
||||
alt=""
|
||||
class="error-view__illustration"
|
||||
src="./assets/images/empty-category.svg"
|
||||
/>
|
||||
|
||||
+1
@@ -112,6 +112,7 @@ describe('WorkspaceShellSearchSyncService', () => {
|
||||
setSearchTerm: jest.fn(),
|
||||
setCategorySearchTerm: jest.fn(),
|
||||
getSelectedCategory: signal(null),
|
||||
selectedCategoryId: signal(null),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
+4
-1
@@ -156,7 +156,10 @@ export class WorkspaceShellSearchService {
|
||||
context,
|
||||
section,
|
||||
translate: (key, params) => this.translateText(key, params),
|
||||
xtreamCategory: this.xtreamStore.getSelectedCategory(),
|
||||
// No selected category means the grid lists every item.
|
||||
xtreamCategory: this.xtreamStore.selectedCategoryId()
|
||||
? this.xtreamStore.getSelectedCategory()
|
||||
: { name: this.translateText('PORTALS.ALL_ITEMS') },
|
||||
stalkerCategoryName:
|
||||
this.stalkerStore.getSelectedCategoryName(),
|
||||
}),
|
||||
|
||||
+4
-1
@@ -56,7 +56,10 @@ describe('WorkspaceShellSearchService on settings routes', () => {
|
||||
{ provide: Router, useValue: { navigate: jest.fn() } },
|
||||
{
|
||||
provide: XtreamStore,
|
||||
useValue: { getSelectedCategory: () => null },
|
||||
useValue: {
|
||||
getSelectedCategory: () => null,
|
||||
selectedCategoryId: () => null,
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: StalkerStore,
|
||||
|
||||
+22
@@ -58,6 +58,7 @@ class MockXtreamStore {
|
||||
category_name?: string;
|
||||
name?: string;
|
||||
} | null>(null);
|
||||
readonly selectedCategoryId = signal<number | null>(null);
|
||||
readonly vodStreams = signal<unknown[]>([{ id: 1 }]);
|
||||
readonly liveStreams = signal<unknown[]>([{ id: 2 }]);
|
||||
readonly serialStreams = signal<unknown[]>([{ id: 3 }]);
|
||||
@@ -669,6 +670,27 @@ describe('WorkspaceShellFacade', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('scopes Xtream search to the translated every-item label without a category', () => {
|
||||
const xtreamStore = TestBed.inject(
|
||||
XtreamStore
|
||||
) as unknown as MockXtreamStore;
|
||||
|
||||
facade.currentUrl.set('/workspace/xtreams/pl-1/vod?q=neo');
|
||||
searchSync.syncSearchFromRoute();
|
||||
TestBed.flushEffects();
|
||||
|
||||
expect(facade.searchScopeLabel()).toBe(
|
||||
'WORKSPACE.SHELL.RAIL_MOVIES / PORTALS.ALL_ITEMS'
|
||||
);
|
||||
|
||||
xtreamStore.selectedCategoryId.set(7);
|
||||
xtreamStore.getSelectedCategory.set({ name: 'Drama' });
|
||||
|
||||
expect(facade.searchScopeLabel()).toBe(
|
||||
'WORKSPACE.SHELL.RAIL_MOVIES / Drama'
|
||||
);
|
||||
});
|
||||
|
||||
it('applies q to Xtream category search on vod routes', () => {
|
||||
const xtreamStore = TestBed.inject(
|
||||
XtreamStore
|
||||
|
||||
Reference in new issue
Block a user