mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
feat(stalker): protocol-correct auth lifecycle (#1354)
This commit is contained in:
1 parent
e197409b10
commit
d2a83164ec
56 files changed
+4807
-709
No files matched your search
@@ -6,6 +6,8 @@
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import { ipcMain } from 'electron';
|
||||
import {
|
||||
classifyStalkerAuthFailureBody,
|
||||
createStalkerAuthFailureMarker,
|
||||
PortalDebugEvent,
|
||||
STALKER_REQUEST,
|
||||
buildStalkerIdentityRequestContext,
|
||||
@@ -111,6 +113,18 @@ ipcMain.handle(
|
||||
throw httpError;
|
||||
}
|
||||
|
||||
// The portal answers auth failures with HTTP 200 and a plain
|
||||
// text/html body ("Authorization failed.", "Access denied.",
|
||||
// "Unauthorized request."). Classify them here so the renderer
|
||||
// receives a structured marker instead of pattern-matching raw
|
||||
// response text.
|
||||
const authFailureBody = classifyStalkerAuthFailureBody(
|
||||
response.data
|
||||
);
|
||||
const responseData = authFailureBody
|
||||
? createStalkerAuthFailureMarker(authFailureBody)
|
||||
: response.data;
|
||||
|
||||
// Return the response data
|
||||
if (
|
||||
params.action === 'create_link' &&
|
||||
@@ -136,12 +150,12 @@ ipcMain.handle(
|
||||
durationMs: Date.now() - startedAt,
|
||||
status: 'success',
|
||||
request: debugRequest,
|
||||
response: response.data,
|
||||
response: responseData,
|
||||
};
|
||||
emitPortalDebugEvent(debugEvent);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
return responseData;
|
||||
} catch (error) {
|
||||
if (payload.requestId) {
|
||||
const debugEvent: PortalDebugEvent = {
|
||||
|
||||
@@ -82,7 +82,7 @@ actually get wrong:
|
||||
| `00:1A:79:00:00:05` | **embedded-series** | 50% of VOD items have embedded `series[]` arrays — tests the embedded series flow |
|
||||
| `00:1A:79:00:00:06` | **legacy-pagination** | No `get_all_channels` support — tests the paginated `get_ordered_list` crawl fallback for the full ITV channel list |
|
||||
| `00:1A:79:00:00:07` | **marketing-demo** | 35 original poster movies with the newest 20 first — safe for screenshots and marketing |
|
||||
| `00:1A:79:00:00:08` | **login-required** | `get_profile` answers `status: 2` until the client completes `do_auth` with non-empty credentials. The app cannot finish this flow yet (its `do_auth` path is dormant and sends empty credentials), so the scenario is exercised at the HTTP level only — it exists to receive the upcoming client-side `do_auth` work |
|
||||
| `00:1A:79:00:00:08` | **login-required** | `get_profile` answers `status: 2` until the client completes `do_auth` with non-empty credentials. The app drives this end to end: enter a username and password in the Stalker import dialog and it runs `do_auth`, then retries `get_profile` with `auth_second_step=1`. Covered by `stalker.e2e.ts` (`@stalker full portal authentication`) |
|
||||
| `00:1A:79:00:00:09` | **gated-stream** | `create_link` returns a local `/stream/gated/…` URL that answers 403 without the mac cookie and the MAC's current Bearer token — proves a player's media requests really carry the portal credentials |
|
||||
| `00:1A:79:00:00:0A` | **static-channel-cmd** | ITV rows carry a directly playable `cmd` with `use_http_tmp_link`/`use_load_balancing` both `'0'` — a client honouring the flags must play them without calling `create_link` |
|
||||
| `<any other MAC>` | **auto** | MAC bytes used as seed → deterministic unique dataset |
|
||||
|
||||
@@ -11,10 +11,9 @@ import { handleHandshake } from './handshake.handler';
|
||||
/**
|
||||
* Handler-level coverage for the authentication actions.
|
||||
*
|
||||
* The e2e suite drives the app against this server, but the login-required
|
||||
* flow cannot be reached from the client yet (its `do_auth` path is dormant
|
||||
* and sends empty credentials), so the scenario is pinned down here rather
|
||||
* than only asserted in prose.
|
||||
* The e2e suite drives the app through this flow end to end; these handler
|
||||
* tests pin the SERVER half of the contract independently, so a client-side
|
||||
* regression and a mock-side one cannot mask each other.
|
||||
*
|
||||
* Handlers are called directly instead of through the dispatcher: the
|
||||
* dispatcher pulls in every content handler and therefore the faker-based
|
||||
@@ -82,8 +81,8 @@ describe('stalker mock authentication handlers', () => {
|
||||
})['token'] as string;
|
||||
expect(token).toMatch(/^[0-9A-F]{32}$/);
|
||||
|
||||
// The app sends auth_second_step=1 on its very first profile request,
|
||||
// so that parameter alone must not satisfy the scenario.
|
||||
// A client could mislabel its first profile as the second auth step;
|
||||
// that parameter alone must not satisfy the scenario.
|
||||
expect(
|
||||
invoke(
|
||||
handleGetProfile,
|
||||
|
||||
@@ -33,10 +33,11 @@ export function handleGetProfile(
|
||||
return;
|
||||
}
|
||||
|
||||
// Gate on the actual do_auth completion, not on auth_second_step: the app
|
||||
// sends auth_second_step=1 on its very first get_profile, so a parameter
|
||||
// check would let the login-required flow be bypassed without ever
|
||||
// exercising status 2 -> do_auth -> profile retry.
|
||||
// Gate on the actual do_auth completion, not on auth_second_step. The
|
||||
// client now sends `auth_second_step=0` first and `1` only on the retry
|
||||
// after do_auth, so a parameter check would happen to work — but it would
|
||||
// also silently pass for any client that mislabels its first profile,
|
||||
// which is exactly the protocol mistake this scenario exists to catch.
|
||||
if (scenario.requiresLogin && !hasCompletedDoAuth(mac)) {
|
||||
res.json({
|
||||
js: {
|
||||
|
||||
+243
-21
@@ -48,6 +48,11 @@ import {
|
||||
* `--project=chromium` alone (`.github/workflows/e2e-tests.yaml`). If a local
|
||||
* all-project run shows a lone auth failure that passes on rerun, this is why;
|
||||
* `--project=chromium` reproduces CI exactly.
|
||||
*
|
||||
* Most tests here tolerate that reset anyway — they re-authenticate, or assert
|
||||
* that a NEW token appears. The token-reuse test cannot: its assertion IS that
|
||||
* the portal session survives, so it uses per-project MACs held outside
|
||||
* `OWNED_MACS`. See `AUTH_REUSE_MACS`.
|
||||
*/
|
||||
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
@@ -82,6 +87,13 @@ const LEGACY_PAGINATION_MAC = '00:1A:79:00:00:06';
|
||||
*/
|
||||
const STATIC_CMD_MAC = '00:1A:79:00:00:0A';
|
||||
|
||||
/**
|
||||
* Login-required scenario MAC — get_profile answers status 2 until do_auth
|
||||
* completes with non-empty credentials (empty credentials return {js:false},
|
||||
* as the operator billing script would).
|
||||
*/
|
||||
const LOGIN_REQUIRED_MAC = '00:1A:79:00:00:08';
|
||||
|
||||
/**
|
||||
* Dedicated MACs for the full-portal authentication tests. Mock state is keyed
|
||||
* by MAC, so keeping these distinct from the content scenarios above means an
|
||||
@@ -90,6 +102,25 @@ const STATIC_CMD_MAC = '00:1A:79:00:00:0A';
|
||||
*/
|
||||
const AUTH_FLOW_MAC = '00:1A:79:AD:00:01';
|
||||
const AUTH_REAUTH_MAC = '00:1A:79:AD:00:03';
|
||||
|
||||
/**
|
||||
* The token-reuse test needs the portal session to SURVIVE untouched between
|
||||
* its import and its reload — that persistence is the whole assertion. Every
|
||||
* other test here is reset-tolerant (they either re-authenticate or assert a
|
||||
* new token), so they can share `OWNED_MACS`, which each `beforeEach` clears.
|
||||
*
|
||||
* `mode: 'serial'` only serializes within one project; the browser projects
|
||||
* still run concurrently, so a sibling project's reset would rotate this
|
||||
* session mid-test. Hence one MAC per project, and deliberately NOT in
|
||||
* `OWNED_MACS`: nothing may reset them. Leftover state from an earlier run is
|
||||
* harmless — the import negotiates and adopts its own token first.
|
||||
*/
|
||||
const AUTH_REUSE_MACS: Record<string, string> = {
|
||||
chromium: '00:1A:79:AD:01:01',
|
||||
firefox: '00:1A:79:AD:01:02',
|
||||
webkit: '00:1A:79:AD:01:03',
|
||||
};
|
||||
const AUTH_REUSE_FALLBACK_MAC = '00:1A:79:AD:01:04';
|
||||
/**
|
||||
* Deliberately NOT an Infomir MAC: the strict endpoint rejects get_profile for
|
||||
* it, so no token is ever adopted and content requests fail permanently.
|
||||
@@ -138,6 +169,7 @@ const OWNED_MACS = [
|
||||
EMBEDDED_SERIES_MAC,
|
||||
LEGACY_PAGINATION_MAC,
|
||||
STATIC_CMD_MAC,
|
||||
LOGIN_REQUIRED_MAC,
|
||||
AUTH_FLOW_MAC,
|
||||
AUTH_REAUTH_MAC,
|
||||
AUTH_REJECTED_MAC,
|
||||
@@ -212,9 +244,21 @@ async function addStalkerPortal(
|
||||
*/
|
||||
async function addFullStalkerPortal(
|
||||
page: Page,
|
||||
options: { name?: string; mac: string; expectContent?: boolean }
|
||||
options: {
|
||||
name?: string;
|
||||
mac: string;
|
||||
expectContent?: boolean;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
): Promise<void> {
|
||||
const { name = 'Full Stalker Portal', mac, expectContent = true } = options;
|
||||
const {
|
||||
name = 'Full Stalker Portal',
|
||||
mac,
|
||||
expectContent = true,
|
||||
username,
|
||||
password,
|
||||
} = options;
|
||||
|
||||
await page.getByRole('button', { name: 'Add playlist' }).click();
|
||||
const dialog = page.locator('mat-dialog-container');
|
||||
@@ -224,6 +268,12 @@ async function addFullStalkerPortal(
|
||||
await setInputValue(dialog.locator('input#title'), name);
|
||||
await setInputValue(dialog.locator('input#portalUrl'), FULL_PORTAL_URL);
|
||||
await setInputValue(dialog.locator('input#macAddress'), mac);
|
||||
if (username !== undefined) {
|
||||
await setInputValue(dialog.locator('input#username'), username);
|
||||
}
|
||||
if (password !== undefined) {
|
||||
await setInputValue(dialog.locator('input#password'), password);
|
||||
}
|
||||
|
||||
const addButton = dialog.getByRole('button', { name: 'Add', exact: true });
|
||||
await expect(addButton).toBeEnabled({ timeout: 10_000 });
|
||||
@@ -991,12 +1041,171 @@ test.describe('@stalker full portal authentication', () => {
|
||||
.toBe(true);
|
||||
});
|
||||
|
||||
test('never surfaces the portal plain-text auth failure as content', async ({
|
||||
test('completes the documented login flow behind get_profile status 2', async ({
|
||||
page,
|
||||
}) => {
|
||||
const requests = recordPortalRequests(page);
|
||||
// The second auth step must only be claimed on the retry AFTER
|
||||
// do_auth — a client that always sends auth_second_step=1 works
|
||||
// against the mock but violates the documented protocol.
|
||||
const profileSteps: string[] = [];
|
||||
page.on('request', (request) => {
|
||||
const url = new URL(request.url());
|
||||
if (!url.pathname.endsWith('/stalker')) {
|
||||
return;
|
||||
}
|
||||
if (url.searchParams.get('action') !== 'get_profile') {
|
||||
return;
|
||||
}
|
||||
profileSteps.push(url.searchParams.get('auth_second_step') ?? '');
|
||||
});
|
||||
|
||||
await addFullStalkerPortal(page, {
|
||||
mac: LOGIN_REQUIRED_MAC,
|
||||
username: 'user',
|
||||
password: 'secret',
|
||||
});
|
||||
|
||||
// The mock only adopts the token (and answers content) after do_auth
|
||||
// completed with non-empty credentials, so rendered categories prove
|
||||
// the whole status 2 -> do_auth -> profile retry chain ran.
|
||||
await expect(page.locator('.category-item').first()).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
||||
const actions = requests.map((entry) => entry.action);
|
||||
expect(actions).toContain('do_auth');
|
||||
expect(actions.indexOf('get_profile')).toBeLessThan(
|
||||
actions.indexOf('do_auth')
|
||||
);
|
||||
expect(actions.lastIndexOf('get_profile')).toBeGreaterThan(
|
||||
actions.indexOf('do_auth')
|
||||
);
|
||||
|
||||
expect(profileSteps[0]).toBe('0');
|
||||
expect(profileSteps).toContain('1');
|
||||
|
||||
// Content only flows once the token is adopted, which the mock does
|
||||
// only after do_auth completed. Look AFTER the last get_profile:
|
||||
// discovery's classification probe is itself a token-less
|
||||
// `get_genres`, so the first CONTENT_ACTIONS hit is that probe.
|
||||
const contentRequest = requests
|
||||
.slice(actions.lastIndexOf('get_profile') + 1)
|
||||
.find((entry) => CONTENT_ACTIONS.includes(entry.action));
|
||||
expect(contentRequest).toBeDefined();
|
||||
expect(contentRequest?.token).toBeTruthy();
|
||||
});
|
||||
|
||||
test('explains a login-required portal and recovers once credentials are entered', async ({
|
||||
page,
|
||||
}) => {
|
||||
// First attempt without credentials: the import must fail with the
|
||||
// portal's actual reason, not a generic "check URL and MAC" error.
|
||||
await page.getByRole('button', { name: 'Add playlist' }).click();
|
||||
const dialog = page.locator('mat-dialog-container');
|
||||
await expect(dialog).toBeVisible();
|
||||
await dialog.getByRole('radio', { name: /Stalker portal/i }).click();
|
||||
|
||||
await setInputValue(dialog.locator('input#title'), 'Login Portal');
|
||||
await setInputValue(dialog.locator('input#portalUrl'), FULL_PORTAL_URL);
|
||||
await setInputValue(
|
||||
dialog.locator('input#macAddress'),
|
||||
LOGIN_REQUIRED_MAC
|
||||
);
|
||||
|
||||
const addButton = dialog.getByRole('button', {
|
||||
name: 'Add',
|
||||
exact: true,
|
||||
});
|
||||
await expect(addButton).toBeEnabled({ timeout: 10_000 });
|
||||
await addButton.click();
|
||||
|
||||
await expect(
|
||||
page.getByText(/requires a login and password/i)
|
||||
).toBeVisible({ timeout: 15_000 });
|
||||
// The dialog stays open so the user can add the credentials.
|
||||
await expect(dialog).toBeVisible();
|
||||
|
||||
// Second attempt with credentials in the same dialog succeeds.
|
||||
await setInputValue(dialog.locator('input#username'), 'user');
|
||||
await setInputValue(dialog.locator('input#password'), 'secret');
|
||||
await expect(addButton).toBeEnabled({ timeout: 10_000 });
|
||||
await addButton.click();
|
||||
await expect(dialog).toBeHidden({ timeout: 30_000 });
|
||||
|
||||
await page.waitForURL(/stalker.*vod/, { timeout: 30_000 });
|
||||
await expect(page.locator('.category-item').first()).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
});
|
||||
|
||||
test('reuses the persisted token after a reload instead of re-running get_profile', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
const requests = recordPortalRequests(page);
|
||||
const mac =
|
||||
AUTH_REUSE_MACS[testInfo.project.name] ?? AUTH_REUSE_FALLBACK_MAC;
|
||||
|
||||
await addFullStalkerPortal(page, { mac });
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
requests.some(
|
||||
(entry) =>
|
||||
CONTENT_ACTIONS.includes(entry.action) &&
|
||||
entry.token
|
||||
),
|
||||
{ timeout: 30_000 }
|
||||
)
|
||||
.toBe(true);
|
||||
|
||||
const sessionToken = requests.find(
|
||||
(entry) => CONTENT_ACTIONS.includes(entry.action) && entry.token
|
||||
)?.token;
|
||||
expect(sessionToken).toBeTruthy();
|
||||
|
||||
const requestCountBeforeReload = requests.length;
|
||||
await page.reload();
|
||||
|
||||
// Wait on the UI rather than the request log: a reload restores the
|
||||
// route, boots the app and re-authenticates before the first content
|
||||
// request goes out, and rendered categories prove that whole chain
|
||||
// finished (the portal only answers content for an adopted token).
|
||||
await expect(page.locator('.category-item').first()).toBeVisible({
|
||||
timeout: 60_000,
|
||||
});
|
||||
|
||||
const afterReload = requests.slice(requestCountBeforeReload);
|
||||
|
||||
// Content came back under the SAME token, negotiated by re-presenting
|
||||
// the persisted one in the handshake.
|
||||
expect(
|
||||
afterReload.filter(
|
||||
(entry) =>
|
||||
CONTENT_ACTIONS.includes(entry.action) &&
|
||||
entry.token === sessionToken
|
||||
).length
|
||||
).toBeGreaterThan(0);
|
||||
|
||||
// The handshake re-presented the persisted token...
|
||||
const reloadHandshake = afterReload.find(
|
||||
(entry) => entry.action === 'handshake'
|
||||
);
|
||||
expect(reloadHandshake?.token).toBe(sessionToken);
|
||||
|
||||
// ...and because the portal returned it unchanged (idempotent
|
||||
// handshake, still-adopted session), the profile round trip was
|
||||
// skipped entirely.
|
||||
expect(
|
||||
afterReload.filter((entry) => entry.action === 'get_profile')
|
||||
).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('refuses a rejected portal at import and never renders its plain-text failure', async ({
|
||||
page,
|
||||
request,
|
||||
}) => {
|
||||
const requests = recordPortalRequests(page);
|
||||
|
||||
// A MAC outside the Infomir OUI makes the strict endpoint answer
|
||||
// get_profile with a bare {status:1}, so no token is ever adopted and
|
||||
// every content request keeps returning the plain-text failure. Unlike
|
||||
@@ -1014,25 +1223,38 @@ test.describe('@stalker full portal authentication', () => {
|
||||
).json();
|
||||
expect(failureBody.payload).toBe('Authorization failed.');
|
||||
|
||||
await addFullStalkerPortal(page, {
|
||||
mac: AUTH_REJECTED_MAC,
|
||||
expectContent: false,
|
||||
await page.getByRole('button', { name: 'Add playlist' }).click();
|
||||
const dialog = page.locator('mat-dialog-container');
|
||||
await expect(dialog).toBeVisible();
|
||||
await dialog.getByRole('radio', { name: /Stalker portal/i }).click();
|
||||
|
||||
await setInputValue(dialog.locator('input#title'), 'Rejected Portal');
|
||||
await setInputValue(dialog.locator('input#portalUrl'), FULL_PORTAL_URL);
|
||||
await setInputValue(
|
||||
dialog.locator('input#macAddress'),
|
||||
AUTH_REJECTED_MAC
|
||||
);
|
||||
|
||||
const addButton = dialog.getByRole('button', {
|
||||
name: 'Add',
|
||||
exact: true,
|
||||
});
|
||||
await expect(addButton).toBeEnabled({ timeout: 10_000 });
|
||||
await addButton.click();
|
||||
|
||||
// The app must have actually hit the failing portal...
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
requests.filter((entry) =>
|
||||
CONTENT_ACTIONS.includes(entry.action)
|
||||
).length,
|
||||
{ timeout: 30_000 }
|
||||
)
|
||||
.toBeGreaterThan(0);
|
||||
// `status: 1` means the portal refused this device, so the import must
|
||||
// stop there instead of persisting a portal that can never load. (It
|
||||
// used to be treated as success whenever the portal sent no msg text,
|
||||
// which imported a dead source.)
|
||||
await expect(page.getByText(/refused access/i)).toBeVisible({
|
||||
timeout: 15_000,
|
||||
});
|
||||
await expect(dialog).toBeVisible();
|
||||
await expect(page).not.toHaveURL(/stalker/);
|
||||
|
||||
// ...and must never render the raw portal response as content. A
|
||||
// portal answers auth failures with HTTP 200 + plain text, so an app
|
||||
// that trusts the status code would happily paint these strings.
|
||||
// And the raw portal response is never painted as UI. A portal answers
|
||||
// auth failures with HTTP 200 + plain text, so an app that trusts the
|
||||
// status code would happily render these strings.
|
||||
await expect(page.locator('body')).not.toContainText(
|
||||
'Authorization failed.'
|
||||
);
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "رابط الخادم",
|
||||
"URL_VALIDATION_ERROR": "يجب أن يكون الرابط صالحًا مع بروتوكول (مثال: http://example.com/stalker_portal)",
|
||||
"ADD": "إضافة",
|
||||
"VALIDATING": "جارٍ التحقق من البوابة..."
|
||||
"VALIDATING": "جارٍ التحقق من البوابة...",
|
||||
"CREDENTIALS_HINT": "مطلوب فقط عندما تطلب البوابة اسم مستخدم وكلمة مرور",
|
||||
"AUTH_FAILED": "فشلت المصادقة مع البوابة. تحقق من الرابط وعنوان MAC.",
|
||||
"LOGIN_REQUIRED": "تتطلب هذه البوابة اسم مستخدم وكلمة مرور. املأ حقلي اسم المستخدم وكلمة المرور وأعد المحاولة.",
|
||||
"LOGIN_REJECTED": "رفضت البوابة اسم المستخدم وكلمة المرور.",
|
||||
"PORTAL_REFUSED": "رفضت البوابة الوصول لهذا الجهاز.",
|
||||
"PORTAL_MESSAGE": "أفادت البوابة: {{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "التصفية حسب الاسم",
|
||||
"FILTER_AND_SORT": "تصفية وفرز",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "لم يتم تحديد فئة",
|
||||
"DESCRIPTION": "يرجى اختيار فئة لعرض المحتوى"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "تتطلب البوابة اسم مستخدم وكلمة مرور. أعد استيراد البوابة واملأ حقلي اسم المستخدم وكلمة المرور.",
|
||||
"PLAYLIST_SETTINGS": "إعدادات القائمة",
|
||||
"HOME": "الرئيسية",
|
||||
"DELETE": "حذف"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "رابط الخادم",
|
||||
"URL_VALIDATION_ERROR": "خاص يكون رابط صحيح مع البروتوكول (مثلا http://example.com/c ولا https://example.com/stalker_portal/c)",
|
||||
"ADD": "زيد",
|
||||
"VALIDATING": "جاري التحقق من البوابة..."
|
||||
"VALIDATING": "جاري التحقق من البوابة...",
|
||||
"CREDENTIALS_HINT": "خاصين غير إذا البوابة كتطلب اسم المستخدم وكلمة المرور",
|
||||
"AUTH_FAILED": "فشل تسجيل الدخول للبوابة. تحقق من الرابط وعنوان MAC.",
|
||||
"LOGIN_REQUIRED": "هاد البوابة كتطلب اسم المستخدم وكلمة المرور. عمّر خانات اسم المستخدم وكلمة المرور وحاول مرة أخرى.",
|
||||
"LOGIN_REJECTED": "البوابة رفضات اسم المستخدم وكلمة المرور.",
|
||||
"PORTAL_REFUSED": "البوابة رفضات الوصول لهاد الجهاز.",
|
||||
"PORTAL_MESSAGE": "البوابة قالت: {{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "فلتر بالاسم",
|
||||
"FILTER_AND_SORT": "فلتر وترتيب",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "ما كاين حتى فئة متختارة",
|
||||
"DESCRIPTION": "عفاك اختار فئة باش تشوف المحتوى"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "البوابة كتطلب اسم المستخدم وكلمة المرور. عاود استيراد البوابة وعمّر خانات اسم المستخدم وكلمة المرور.",
|
||||
"PLAYLIST_SETTINGS": "إعدادات قائمة التشغيل",
|
||||
"HOME": "الرئيسية",
|
||||
"DELETE": "حذف"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "URL сервера",
|
||||
"URL_VALIDATION_ERROR": "Павінен быць сапраўдны URL-адрас з пратаколам (напрыклад, http://example.com/c або https://example.com/stalker_portal/c).",
|
||||
"ADD": "Дадаць",
|
||||
"VALIDATING": "Праверка партала..."
|
||||
"VALIDATING": "Праверка партала...",
|
||||
"CREDENTIALS_HINT": "Патрэбна толькі тады, калі партал запытвае лагін і пароль",
|
||||
"AUTH_FAILED": "Не ўдалося аўтэнтыфікавацца на партале. Праверце URL і MAC-адрас.",
|
||||
"LOGIN_REQUIRED": "Гэты партал патрабуе лагін і пароль. Запоўніце палі імя карыстальніка і пароля і паспрабуйце яшчэ раз.",
|
||||
"LOGIN_REJECTED": "Партал адхіліў лагін і пароль.",
|
||||
"PORTAL_REFUSED": "Партал адмовіў у доступе для гэтай прылады.",
|
||||
"PORTAL_MESSAGE": "Партал паведаміў: {{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "Фільтраваць па імені",
|
||||
"FILTER_AND_SORT": "Фільтр і сартаванне",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "Катэгорыя не выбрана",
|
||||
"DESCRIPTION": "Калі ласка, выберыце катэгорыю, каб убачыць кантэнт"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "Партал патрабуе лагін і пароль. Імпартуйце партал яшчэ раз і запоўніце палі імя карыстальніка і пароля.",
|
||||
"PLAYLIST_SETTINGS": "Налады плэйліста",
|
||||
"HOME": "На галоўную",
|
||||
"DELETE": "Выдаліць"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "Server-URL",
|
||||
"URL_VALIDATION_ERROR": "Sollte eine gültige URL mit Protokoll sein (z. B. http://example.com/c oder https://example.com/stalker_portal/c)",
|
||||
"ADD": "Hinzufügen",
|
||||
"VALIDATING": "Portal wird überprüft …"
|
||||
"VALIDATING": "Portal wird überprüft …",
|
||||
"CREDENTIALS_HINT": "Nur erforderlich, wenn das Portal Benutzername und Passwort verlangt",
|
||||
"AUTH_FAILED": "Authentifizierung am Portal fehlgeschlagen. Überprüfen Sie die URL und die MAC-Adresse.",
|
||||
"LOGIN_REQUIRED": "Dieses Portal erfordert Benutzername und Passwort. Füllen Sie die Felder Benutzername und Passwort aus und versuchen Sie es erneut.",
|
||||
"LOGIN_REJECTED": "Das Portal hat Benutzername und Passwort abgelehnt.",
|
||||
"PORTAL_REFUSED": "Das Portal hat den Zugriff für dieses Gerät verweigert.",
|
||||
"PORTAL_MESSAGE": "Das Portal meldet: {{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "Nach Namen filtern",
|
||||
"FILTER_AND_SORT": "Filtern & Sortieren",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "Keine Kategorie ausgewählt",
|
||||
"DESCRIPTION": "Bitte wählen Sie eine Kategorie aus, um den Inhalt anzuzeigen"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "Das Portal erfordert Benutzername und Passwort. Importieren Sie das Portal erneut und füllen Sie die Felder Benutzername und Passwort aus.",
|
||||
"PLAYLIST_SETTINGS": "Playlist-Einstellungen",
|
||||
"HOME": "Startseite",
|
||||
"DELETE": "Löschen"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "Διεύθηνση URL σέρβερ",
|
||||
"URL_VALIDATION_ERROR": "Θα πρέπει να είναι μια έγκυρη διεύθυνση URL με πρωτόκολλο (π.χ. http://example.com/c ή https://example.com/stalker_portal/c)",
|
||||
"ADD": "Προσθήκη",
|
||||
"VALIDATING": "Επικύρωση πύλης..."
|
||||
"VALIDATING": "Επικύρωση πύλης...",
|
||||
"CREDENTIALS_HINT": "Απαιτείται μόνο όταν η πύλη ζητά όνομα χρήστη και κωδικό πρόσβασης",
|
||||
"AUTH_FAILED": "Η ταυτοποίηση με την πύλη απέτυχε. Ελέγξτε τη διεύθυνση URL και τη διεύθυνση MAC.",
|
||||
"LOGIN_REQUIRED": "Αυτή η πύλη απαιτεί όνομα χρήστη και κωδικό πρόσβασης. Συμπληρώστε τα πεδία ονόματος χρήστη και κωδικού πρόσβασης και δοκιμάστε ξανά.",
|
||||
"LOGIN_REJECTED": "Η πύλη απέρριψε το όνομα χρήστη και τον κωδικό πρόσβασης.",
|
||||
"PORTAL_REFUSED": "Η πύλη αρνήθηκε την πρόσβαση για αυτήν τη συσκευή.",
|
||||
"PORTAL_MESSAGE": "Η πύλη ανέφερε: {{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "Φιλτράρισμα κατά όνομα",
|
||||
"FILTER_AND_SORT": "Φιλτράρισμα & Ταξινόμηση",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "Δεν έχει επιλεγεί κατηγορία",
|
||||
"DESCRIPTION": "Επιλέξτε μια κατηγορία για να δείτε το περιεχόμενο"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "Η πύλη απαιτεί όνομα χρήστη και κωδικό πρόσβασης. Εισαγάγετε ξανά την πύλη και συμπληρώστε τα πεδία ονόματος χρήστη και κωδικού πρόσβασης.",
|
||||
"PLAYLIST_SETTINGS": "Ρυθμίσεις λίστας αναπαραγωγής",
|
||||
"HOME": "Αρχικό",
|
||||
"DELETE": "Διαγραφή"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "Server URL",
|
||||
"URL_VALIDATION_ERROR": "Should be a valid url with protocol (e.g. http://example.com/c or https://example.com/stalker_portal/c)",
|
||||
"ADD": "Add",
|
||||
"VALIDATING": "Validating portal..."
|
||||
"VALIDATING": "Validating portal...",
|
||||
"CREDENTIALS_HINT": "Only needed when the portal asks for a login and password",
|
||||
"AUTH_FAILED": "Failed to authenticate with the portal. Check the URL and MAC address.",
|
||||
"LOGIN_REQUIRED": "This portal requires a login and password. Fill in the username and password fields and try again.",
|
||||
"LOGIN_REJECTED": "The portal rejected the login and password.",
|
||||
"PORTAL_REFUSED": "The portal refused access for this device.",
|
||||
"PORTAL_MESSAGE": "The portal reported: {{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "Filter by name",
|
||||
"FILTER_AND_SORT": "Filter & Sort",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "No category selected",
|
||||
"DESCRIPTION": "Please select a category to see the content"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "The portal requires a login and password. Import the portal again and fill in the username and password fields.",
|
||||
"PLAYLIST_SETTINGS": "Playlist Settings",
|
||||
"HOME": "Home",
|
||||
"DELETE": "Delete"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "URL del servidor",
|
||||
"URL_VALIDATION_ERROR": "Debe ser una URL válida con protocolo (por ejemplo, http://example.com/c o https://example.com/stalker_portal/c).",
|
||||
"ADD": "Agregar",
|
||||
"VALIDATING": "Validando portal…"
|
||||
"VALIDATING": "Validando portal…",
|
||||
"CREDENTIALS_HINT": "Solo es necesario cuando el portal pide usuario y contraseña",
|
||||
"AUTH_FAILED": "No se pudo autenticar con el portal. Verifica la URL y la dirección MAC.",
|
||||
"LOGIN_REQUIRED": "Este portal requiere usuario y contraseña. Completa los campos de nombre de usuario y contraseña e inténtalo de nuevo.",
|
||||
"LOGIN_REJECTED": "El portal rechazó el usuario y la contraseña.",
|
||||
"PORTAL_REFUSED": "El portal denegó el acceso a este dispositivo.",
|
||||
"PORTAL_MESSAGE": "El portal informó: {{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "Filtrar por nombre",
|
||||
"FILTER_AND_SORT": "Filtrar y ordenar",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "Ninguna categoría seleccionada",
|
||||
"DESCRIPTION": "Selecciona una categoría para ver el contenido"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "El portal requiere usuario y contraseña. Vuelve a importar el portal y completa los campos de nombre de usuario y contraseña.",
|
||||
"PLAYLIST_SETTINGS": "Configuración de lista de reproducción",
|
||||
"HOME": "Hogar",
|
||||
"DELETE": "Borrar"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "URL du serveur",
|
||||
"URL_VALIDATION_ERROR": "Doit être une URL valide avec un protocole (par exemple http://example.com/c ou https://example.com/stalker_portal/c)",
|
||||
"ADD": "Ajouter",
|
||||
"VALIDATING": "Validation du portail…"
|
||||
"VALIDATING": "Validation du portail…",
|
||||
"CREDENTIALS_HINT": "Nécessaire uniquement lorsque le portail demande un identifiant et un mot de passe",
|
||||
"AUTH_FAILED": "Échec de l'authentification auprès du portail. Vérifiez l'URL et l'adresse MAC.",
|
||||
"LOGIN_REQUIRED": "Ce portail nécessite un identifiant et un mot de passe. Renseignez les champs nom d'utilisateur et mot de passe, puis réessayez.",
|
||||
"LOGIN_REJECTED": "Le portail a rejeté l'identifiant et le mot de passe.",
|
||||
"PORTAL_REFUSED": "Le portail a refusé l'accès pour cet appareil.",
|
||||
"PORTAL_MESSAGE": "Le portail a signalé : {{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "Filtrer par nom",
|
||||
"FILTER_AND_SORT": "Filtrer et trier",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "Aucune catégorie sélectionnée",
|
||||
"DESCRIPTION": "Veuillez sélectionner une catégorie pour voir le contenu"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "Le portail nécessite un identifiant et un mot de passe. Importez à nouveau le portail et renseignez les champs nom d'utilisateur et mot de passe.",
|
||||
"PLAYLIST_SETTINGS": "Paramètres de la liste",
|
||||
"HOME": "Accueil",
|
||||
"DELETE": "Supprimer"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "Kiszolgáló URL-címe",
|
||||
"URL_VALIDATION_ERROR": "Adjon meg protokollt is tartalmazó, érvényes URL-címet (például http://example.com/c vagy https://example.com/stalker_portal/c)",
|
||||
"ADD": "Hozzáadás",
|
||||
"VALIDATING": "Portál ellenőrzése…"
|
||||
"VALIDATING": "Portál ellenőrzése…",
|
||||
"CREDENTIALS_HINT": "Csak akkor szükséges, ha a portál felhasználónevet és jelszót kér",
|
||||
"AUTH_FAILED": "Nem sikerült a hitelesítés a portálon. Ellenőrizze az URL-címet és a MAC-címet.",
|
||||
"LOGIN_REQUIRED": "Ez a portál felhasználónevet és jelszót igényel. Töltse ki a felhasználónév és a jelszó mezőt, majd próbálja újra.",
|
||||
"LOGIN_REJECTED": "A portál elutasította a felhasználónevet és a jelszót.",
|
||||
"PORTAL_REFUSED": "A portál megtagadta a hozzáférést ettől az eszköztől.",
|
||||
"PORTAL_MESSAGE": "A portál a következőt jelezte: {{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "Szűrés név alapján",
|
||||
"FILTER_AND_SORT": "Szűrés és rendezés",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "Nincs kiválasztott kategória",
|
||||
"DESCRIPTION": "A tartalom megjelenítéséhez válasszon egy kategóriát."
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "A portál felhasználónevet és jelszót igényel. Importálja újra a portált, és töltse ki a felhasználónév és a jelszó mezőt.",
|
||||
"PLAYLIST_SETTINGS": "Lejátszási lista beállításai",
|
||||
"HOME": "Kezdőlap",
|
||||
"DELETE": "Törlés"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "URL server",
|
||||
"URL_VALIDATION_ERROR": "Dovrebbe essere una URL valida con protocollo (es. http://esempio.it/c o https://esempio.it/stalker_portal/c)",
|
||||
"ADD": "Aggiungi",
|
||||
"VALIDATING": "Validazione del portale..."
|
||||
"VALIDATING": "Validazione del portale...",
|
||||
"CREDENTIALS_HINT": "Necessario solo se il portale richiede nome utente e password",
|
||||
"AUTH_FAILED": "Impossibile autenticarsi con il portale. Controlla l'URL e l'indirizzo MAC.",
|
||||
"LOGIN_REQUIRED": "Questo portale richiede nome utente e password. Compila i campi nome utente e password e riprova.",
|
||||
"LOGIN_REJECTED": "Il portale ha rifiutato il nome utente e la password.",
|
||||
"PORTAL_REFUSED": "Il portale ha negato l'accesso a questo dispositivo.",
|
||||
"PORTAL_MESSAGE": "Il portale ha segnalato: {{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "Filtra per nome",
|
||||
"FILTER_AND_SORT": "Filtra e ordina",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "Nessuna categoria selezionata",
|
||||
"DESCRIPTION": "Seleziona una categoria per vedere i contenuti"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "Il portale richiede nome utente e password. Importa di nuovo il portale e compila i campi nome utente e password.",
|
||||
"PLAYLIST_SETTINGS": "Impostazioni Playlist",
|
||||
"HOME": "Home",
|
||||
"DELETE": "Elimina"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "サーバーURL",
|
||||
"URL_VALIDATION_ERROR": "プロトコルを含む有効なURLである必要があります(例:http://example.com/c または https://example.com/stalker_portal/c)",
|
||||
"ADD": "追加",
|
||||
"VALIDATING": "ポータルを検証中..."
|
||||
"VALIDATING": "ポータルを検証中...",
|
||||
"CREDENTIALS_HINT": "ポータルがユーザー名とパスワードを要求する場合のみ必要",
|
||||
"AUTH_FAILED": "ポータルでの認証に失敗しました。URLとMACアドレスを確認してください。",
|
||||
"LOGIN_REQUIRED": "このポータルにはユーザー名とパスワードが必要です。ユーザー名とパスワードの欄を入力して、もう一度お試しください。",
|
||||
"LOGIN_REJECTED": "ポータルがユーザー名とパスワードを拒否しました。",
|
||||
"PORTAL_REFUSED": "ポータルがこのデバイスのアクセスを拒否しました。",
|
||||
"PORTAL_MESSAGE": "ポータルからの報告:{{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "名前でフィルター",
|
||||
"FILTER_AND_SORT": "フィルターと並び替え",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "カテゴリーが選択されていません",
|
||||
"DESCRIPTION": "コンテンツを表示するにはカテゴリーを選択してください"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "ポータルにはユーザー名とパスワードが必要です。ポータルを再インポートして、ユーザー名とパスワードの欄を入力してください。",
|
||||
"PLAYLIST_SETTINGS": "プレイリスト設定",
|
||||
"HOME": "ホーム",
|
||||
"DELETE": "削除"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "서버 URL",
|
||||
"URL_VALIDATION_ERROR": "프로토콜이 포함된 유효한 URL이어야 합니다(예: http://example.com/c 또는 https://example.com/stalker_portal/c).",
|
||||
"ADD": "추가하다",
|
||||
"VALIDATING": "포털을 검증하는 중..."
|
||||
"VALIDATING": "포털을 검증하는 중...",
|
||||
"CREDENTIALS_HINT": "포털이 사용자 이름과 비밀번호를 요구하는 경우에만 필요",
|
||||
"AUTH_FAILED": "포털 인증에 실패했습니다. URL과 MAC 주소를 확인하세요.",
|
||||
"LOGIN_REQUIRED": "이 포털에는 사용자 이름과 비밀번호가 필요합니다. 사용자 이름과 비밀번호 필드를 입력한 후 다시 시도하세요.",
|
||||
"LOGIN_REJECTED": "포털이 사용자 이름과 비밀번호를 거부했습니다.",
|
||||
"PORTAL_REFUSED": "포털이 이 기기의 접근을 거부했습니다.",
|
||||
"PORTAL_MESSAGE": "포털에서 보고한 내용: {{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "이름으로 필터",
|
||||
"FILTER_AND_SORT": "필터 및 정렬",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "선택된 카테고리가 없습니다",
|
||||
"DESCRIPTION": "콘텐츠를 보려면 카테고리를 선택하세요"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "포털에 사용자 이름과 비밀번호가 필요합니다. 포털을 다시 가져온 후 사용자 이름과 비밀번호 필드를 입력하세요.",
|
||||
"PLAYLIST_SETTINGS": "재생목록 설정",
|
||||
"HOME": "홈",
|
||||
"DELETE": "삭제"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "Server URL",
|
||||
"URL_VALIDATION_ERROR": "Moet een geldige URL zijn met protocol (bijv. http://example.com/c o https://example.com/stalker_portal/c)",
|
||||
"ADD": "Toevoegen",
|
||||
"VALIDATING": "Portaal valideren..."
|
||||
"VALIDATING": "Portaal valideren...",
|
||||
"CREDENTIALS_HINT": "Alleen nodig als het portaal om een gebruikersnaam en wachtwoord vraagt",
|
||||
"AUTH_FAILED": "Aanmelden bij het portaal is mislukt. Controleer de URL en het MAC-adres.",
|
||||
"LOGIN_REQUIRED": "Dit portaal vereist een gebruikersnaam en wachtwoord. Vul de velden gebruikersnaam en wachtwoord in en probeer het opnieuw.",
|
||||
"LOGIN_REJECTED": "Het portaal heeft de gebruikersnaam en het wachtwoord geweigerd.",
|
||||
"PORTAL_REFUSED": "Het portaal heeft de toegang voor dit apparaat geweigerd.",
|
||||
"PORTAL_MESSAGE": "Het portaal meldde: {{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "Filter op naam",
|
||||
"FILTER_AND_SORT": "Filteren & sorteren",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "Geen categorie geselecteerd",
|
||||
"DESCRIPTION": "Selecteer een categorie om de inhoud te bekijken"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "Het portaal vereist een gebruikersnaam en wachtwoord. Importeer het portaal opnieuw en vul de velden gebruikersnaam en wachtwoord in.",
|
||||
"PLAYLIST_SETTINGS": "Afspeellijst instellingen",
|
||||
"HOME": "Home",
|
||||
"DELETE": "Verwijderen"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "URL serwera",
|
||||
"URL_VALIDATION_ERROR": "Powinien to być poprawny URL z protokołem (np. http://example.com/c lub https://example.com/stalker_portal/c)",
|
||||
"ADD": "Dodaj",
|
||||
"VALIDATING": "Walidacja portalu..."
|
||||
"VALIDATING": "Walidacja portalu...",
|
||||
"CREDENTIALS_HINT": "Potrzebne tylko wtedy, gdy portal wymaga loginu i hasła",
|
||||
"AUTH_FAILED": "Nie udało się uwierzytelnić w portalu. Sprawdź URL i adres MAC.",
|
||||
"LOGIN_REQUIRED": "Ten portal wymaga loginu i hasła. Wypełnij pola nazwy użytkownika i hasła i spróbuj ponownie.",
|
||||
"LOGIN_REJECTED": "Portal odrzucił login i hasło.",
|
||||
"PORTAL_REFUSED": "Portal odmówił dostępu temu urządzeniu.",
|
||||
"PORTAL_MESSAGE": "Portal zgłosił: {{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "Filtruj według nazwy",
|
||||
"FILTER_AND_SORT": "Filtruj i sortuj",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "Nie wybrano kategorii",
|
||||
"DESCRIPTION": "Wybierz kategorię, aby zobaczyć treści"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "Portal wymaga loginu i hasła. Zaimportuj portal ponownie i wypełnij pola nazwy użytkownika i hasła.",
|
||||
"PLAYLIST_SETTINGS": "Ustawienia listy odtwarzania",
|
||||
"HOME": "Strona główna",
|
||||
"DELETE": "Usuń"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "URL do servidor",
|
||||
"URL_VALIDATION_ERROR": "Deve ser uma URL válida com protocolo (por exemplo, http://example.com/c ou https://example.com/stalker_portal/c)",
|
||||
"ADD": "Adicionar",
|
||||
"VALIDATING": "Validando portal..."
|
||||
"VALIDATING": "Validando portal...",
|
||||
"CREDENTIALS_HINT": "Necessário apenas quando o portal exige login e senha",
|
||||
"AUTH_FAILED": "Falha na autenticação com o portal. Verifique a URL e o endereço MAC.",
|
||||
"LOGIN_REQUIRED": "Este portal exige login e senha. Preencha os campos de nome de usuário e senha e tente novamente.",
|
||||
"LOGIN_REJECTED": "O portal rejeitou o login e a senha.",
|
||||
"PORTAL_REFUSED": "O portal recusou o acesso para este dispositivo.",
|
||||
"PORTAL_MESSAGE": "O portal informou: {{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "Filtrar por nome",
|
||||
"FILTER_AND_SORT": "Filtrar e ordenar",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "Nenhuma categoria selecionada",
|
||||
"DESCRIPTION": "Selecione uma categoria para ver o conteúdo"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "O portal exige login e senha. Importe o portal novamente e preencha os campos de nome de usuário e senha.",
|
||||
"PLAYLIST_SETTINGS": "Configurações da playlist",
|
||||
"HOME": "Início",
|
||||
"DELETE": "Excluir"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "URL сервера",
|
||||
"URL_VALIDATION_ERROR": "Должен быть действительный URL-адрес с протоколом (например, http://example.com/c или https://example.com/stalker_portal/c).",
|
||||
"ADD": "Добавить",
|
||||
"VALIDATING": "Проверка портала…"
|
||||
"VALIDATING": "Проверка портала…",
|
||||
"CREDENTIALS_HINT": "Требуется, только если портал запрашивает логин и пароль",
|
||||
"AUTH_FAILED": "Не удалось авторизоваться на портале. Проверьте URL и MAC-адрес.",
|
||||
"LOGIN_REQUIRED": "Этот портал требует логин и пароль. Заполните поля имени пользователя и пароля и повторите попытку.",
|
||||
"LOGIN_REJECTED": "Портал отклонил логин и пароль.",
|
||||
"PORTAL_REFUSED": "Портал отказал в доступе этому устройству.",
|
||||
"PORTAL_MESSAGE": "Портал сообщил: {{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "Фильтровать по имени",
|
||||
"FILTER_AND_SORT": "Фильтр и сортировка",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "Категория не выбрана",
|
||||
"DESCRIPTION": "Пожалуйста, выберите категорию для просмотра содержимого"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "Портал требует логин и пароль. Импортируйте портал заново и заполните поля имени пользователя и пароля.",
|
||||
"PLAYLIST_SETTINGS": "Настройки плейлиста",
|
||||
"HOME": "На главную",
|
||||
"DELETE": "Удалить"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "Sunucu URL'si",
|
||||
"URL_VALIDATION_ERROR": "Geçerli bir URL olmalıdır (örn. http://example.com/c veya https://example.com/stalker_portal/c)",
|
||||
"ADD": "Ekle",
|
||||
"VALIDATING": "Portal doğrulanıyor..."
|
||||
"VALIDATING": "Portal doğrulanıyor...",
|
||||
"CREDENTIALS_HINT": "Yalnızca portal kullanıcı adı ve parola istediğinde gereklidir",
|
||||
"AUTH_FAILED": "Portalda kimlik doğrulaması başarısız oldu. URL'yi ve MAC adresini kontrol edin.",
|
||||
"LOGIN_REQUIRED": "Bu portal kullanıcı adı ve parola gerektiriyor. Kullanıcı adı ve parola alanlarını doldurup tekrar deneyin.",
|
||||
"LOGIN_REJECTED": "Portal, kullanıcı adı ve parolayı reddetti.",
|
||||
"PORTAL_REFUSED": "Portal bu cihaz için erişimi reddetti.",
|
||||
"PORTAL_MESSAGE": "Portal şunu bildirdi: {{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "İsime göre filtrele",
|
||||
"FILTER_AND_SORT": "Filtrele ve Sırala",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "Kategori seçilmedi",
|
||||
"DESCRIPTION": "İçeriği görmek için lütfen bir kategori seçin"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "Portal, kullanıcı adı ve parola gerektiriyor. Portalı yeniden içe aktarın ve kullanıcı adı ile parola alanlarını doldurun.",
|
||||
"PLAYLIST_SETTINGS": "Oynatma Listesi Ayarları",
|
||||
"HOME": "Ana Sayfa",
|
||||
"DELETE": "Sil"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "服务器 URL",
|
||||
"URL_VALIDATION_ERROR": "应该是带有协议的有效网址(例如 http://example.com/c 或 https://example.com/stalker_portal/c)",
|
||||
"ADD": "添加",
|
||||
"VALIDATING": "正在验证门户…"
|
||||
"VALIDATING": "正在验证门户…",
|
||||
"CREDENTIALS_HINT": "仅在门户要求登录名和密码时才需要填写",
|
||||
"AUTH_FAILED": "门户身份验证失败。请检查 URL 和 MAC 地址。",
|
||||
"LOGIN_REQUIRED": "此门户需要登录名和密码。请填写用户名和密码字段后重试。",
|
||||
"LOGIN_REJECTED": "门户拒绝了该登录名和密码。",
|
||||
"PORTAL_REFUSED": "门户拒绝了此设备的访问。",
|
||||
"PORTAL_MESSAGE": "门户返回信息:{{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "按名称筛选",
|
||||
"FILTER_AND_SORT": "筛选与排序",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "未选择分类",
|
||||
"DESCRIPTION": "请选择一个分类以查看内容"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "门户需要登录名和密码。请重新导入该门户并填写用户名和密码字段。",
|
||||
"PLAYLIST_SETTINGS": "播放列表设置",
|
||||
"HOME": "首页",
|
||||
"DELETE": "删除"
|
||||
|
||||
@@ -195,7 +195,13 @@
|
||||
"SERVER_URL": "伺服器網址",
|
||||
"URL_VALIDATION_ERROR": "必須是有效的網址並包含協定(例如 http://example.com/c 或 https://example.com/stalker_portal/c)",
|
||||
"ADD": "新增",
|
||||
"VALIDATING": "正在驗證入口網站..."
|
||||
"VALIDATING": "正在驗證入口網站...",
|
||||
"CREDENTIALS_HINT": "僅在入口網站要求登入名稱與密碼時才需填寫",
|
||||
"AUTH_FAILED": "入口網站驗證失敗。請檢查網址與 MAC 位址。",
|
||||
"LOGIN_REQUIRED": "此入口網站需要登入名稱與密碼。請填寫使用者名稱與密碼欄位後重試。",
|
||||
"LOGIN_REJECTED": "入口網站拒絕了此登入名稱與密碼。",
|
||||
"PORTAL_REFUSED": "入口網站拒絕了此裝置的存取。",
|
||||
"PORTAL_MESSAGE": "入口網站回報:{{message}}"
|
||||
},
|
||||
"FILTER_BY_NAME": "依名稱篩選",
|
||||
"FILTER_AND_SORT": "篩選與排序",
|
||||
@@ -957,6 +963,7 @@
|
||||
"TITLE": "未選擇類別",
|
||||
"DESCRIPTION": "請選擇一個類別以查看內容"
|
||||
},
|
||||
"STALKER_LOGIN_REQUIRED": "入口網站需要登入名稱與密碼。請重新匯入入口網站並填寫使用者名稱與密碼欄位。",
|
||||
"PLAYLIST_SETTINGS": "播放清單設定",
|
||||
"HOME": "首頁",
|
||||
"DELETE": "刪除"
|
||||
|
||||
Reference in new issue
Block a user