refactor(schema): simplify validation and OpenAPI generation (#2709)

This commit is contained in:
Hampus
2026-09-11 23:24:26 +02:00
committed by GitHub
parent 4bc5593f9f
commit de1fd95a99
510 changed files with 29494 additions and 36131 deletions
+2 -3
View File
@@ -7,7 +7,6 @@
"./*": "./*"
},
"scripts": {
"generate": "node -e \"\"",
"test": "vitest run",
"test:watch": "vitest",
"typecheck": "tsgo --noEmit"
@@ -16,7 +15,7 @@
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"@vitest/coverage-v8": "catalog:",
"vite-tsconfig-paths": "catalog:",
"vitest": "catalog:"
"vitest": "catalog:",
"yaml": "catalog:"
}
}
+3 -8
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {createECDH} from 'node:crypto';
import {type ConfigObject, isConfigObject} from '@fluxer/config/src/config_loader/ConfigObject';
import {buildNamedFluxerEnvOverrides} from '@fluxer/config/src/config_loader/EnvironmentOverrides';
import {
buildUrl,
@@ -11,8 +12,6 @@ import {
} from '@fluxer/config/src/EndpointDerivation';
import type {MasterConfig} from '@fluxer/config/src/MasterConfig';
type ConfigObject = Record<string, unknown>;
let cachedConfig: MasterConfig | null = null;
const DEFAULT_PASSKEY_ORIGINS = [
@@ -312,18 +311,14 @@ function defaultConfig(): MasterConfig {
};
}
function isPlainObject(value: unknown): value is ConfigObject {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
function mergeConfig<T>(base: T, overrides: unknown): T {
if (!isPlainObject(base) || !isPlainObject(overrides)) {
if (!isConfigObject(base) || !isConfigObject(overrides)) {
return overrides === undefined ? base : (overrides as T);
}
const out: ConfigObject = {...base};
for (const [key, value] of Object.entries(overrides)) {
const current = out[key];
out[key] = isPlainObject(current) && isPlainObject(value) ? mergeConfig(current, value) : value;
out[key] = isConfigObject(current) && isConfigObject(value) ? mergeConfig(current, value) : value;
}
return out as T;
}
+1 -7
View File
@@ -17,13 +17,7 @@ export function extractKVClientConfig(master: MasterConfig) {
kvUrl: master.internal.kv,
kvMode: master.internal.kv_mode,
kvClusterNodes: master.internal.kv_cluster_nodes,
kvClusterNatMap: master.internal.kv_cluster_nat_map as Record<
string,
{
host: string;
port: number;
}
>,
kvClusterNatMap: master.internal.kv_cluster_nat_map,
};
}
@@ -1,8 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {
buildUrl,
canonicalizeDomain,
type DerivedEndpoints,
type DomainConfig,
deriveDomain,
deriveEndpointsFromDomain,
@@ -12,35 +14,19 @@ import {
import {describe, expect, test} from 'vitest';
describe('buildUrl', () => {
test('omits standard HTTP port (80)', () => {
expect(buildUrl('http', 'example.com', 80, '/path')).toBe('http://example.com/path');
});
test('omits standard HTTPS port (443)', () => {
expect(buildUrl('https', 'example.com', 443, '/path')).toBe('https://example.com/path');
});
test('omits standard WebSocket port (80)', () => {
expect(buildUrl('ws', 'example.com', 80, '/gateway')).toBe('ws://example.com/gateway');
});
test('omits standard secure WebSocket port (443)', () => {
expect(buildUrl('wss', 'example.com', 443, '/gateway')).toBe('wss://example.com/gateway');
});
test('includes non-standard port', () => {
expect(buildUrl('http', 'localhost', 8088, '/api')).toBe('http://localhost:8088/api');
});
test('includes non-standard HTTPS port', () => {
expect(buildUrl('https', 'example.com', 8443, '/api')).toBe('https://example.com:8443/api');
});
test('handles missing port', () => {
expect(buildUrl('https', 'example.com', undefined, '/api')).toBe('https://example.com/api');
});
test('handles missing path', () => {
expect(buildUrl('https', 'example.com', 443)).toBe('https://example.com');
});
test('handles empty path', () => {
expect(buildUrl('https', 'example.com', 443, '')).toBe('https://example.com');
});
test('handles root path', () => {
expect(buildUrl('https', 'example.com', 443, '/')).toBe('https://example.com/');
test.each([
['http', 'example.com', 80, '/path', 'http://example.com/path'],
['https', 'example.com', 443, '/path', 'https://example.com/path'],
['ws', 'example.com', 80, '/gateway', 'ws://example.com/gateway'],
['wss', 'example.com', 443, '/gateway', 'wss://example.com/gateway'],
['http', 'localhost', 8088, '/api', 'http://localhost:8088/api'],
['https', 'example.com', 8443, '/api', 'https://example.com:8443/api'],
['https', 'example.com', undefined, '/api', 'https://example.com/api'],
['https', 'example.com', 443, undefined, 'https://example.com'],
['https', 'example.com', 443, '', 'https://example.com'],
['https', 'example.com', 443, '/', 'https://example.com/'],
] as const)('%s %s:%s%s becomes %s', (scheme, domain, port, path, expected) => {
expect(buildUrl(scheme, domain, port, path)).toBe(expected);
});
});
@@ -50,258 +36,269 @@ describe('deriveDomain', () => {
public_scheme: 'https',
internal_scheme: 'http',
};
test('uses base domain for api endpoint', () => {
expect(deriveDomain('api', baseConfig)).toBe('fluxer.dev');
test.each([
'api',
'api_client',
'app',
'gateway',
'media',
'static_cdn',
'admin',
'docs',
'marketing',
'invite',
'gift',
] as const)('uses the base domain for %s without an override', (endpoint) => {
expect(deriveDomain(endpoint, baseConfig)).toBe('fluxer.dev');
});
test('uses base domain for app endpoint', () => {
expect(deriveDomain('app', baseConfig)).toBe('fluxer.dev');
});
test('uses base domain for gateway endpoint', () => {
expect(deriveDomain('gateway', baseConfig)).toBe('fluxer.dev');
});
test('uses base domain for media endpoint', () => {
expect(deriveDomain('media', baseConfig)).toBe('fluxer.dev');
});
test('uses custom static CDN domain when specified', () => {
const config = {...baseConfig, static_cdn_domain: 'cdn.fluxer.dev'};
expect(deriveDomain('static_cdn', config)).toBe('cdn.fluxer.dev');
});
test('uses base domain for static CDN when custom domain not specified', () => {
expect(deriveDomain('static_cdn', baseConfig)).toBe('fluxer.dev');
});
test('uses custom invite domain when specified', () => {
const config = {...baseConfig, invite_domain: 'fluxer.gg'};
expect(deriveDomain('invite', config)).toBe('fluxer.gg');
});
test('uses base domain for invite when custom domain not specified', () => {
expect(deriveDomain('invite', baseConfig)).toBe('fluxer.dev');
});
test('uses custom gift domain when specified', () => {
const config = {...baseConfig, gift_domain: 'fluxer.gift'};
expect(deriveDomain('gift', config)).toBe('fluxer.gift');
});
test('uses base domain for gift when custom domain not specified', () => {
expect(deriveDomain('gift', baseConfig)).toBe('fluxer.dev');
test.each([
['static_cdn', {static_cdn_domain: 'cdn.fluxer.dev'}, 'cdn.fluxer.dev'],
['invite', {invite_domain: 'fluxer.gg'}, 'fluxer.gg'],
['gift', {gift_domain: 'fluxer.gift'}, 'fluxer.gift'],
] as const)('uses the custom %s domain', (endpoint, overrides, expected) => {
expect(deriveDomain(endpoint, {...baseConfig, ...overrides})).toBe(expected);
});
});
describe('deriveEndpointsFromDomain', () => {
describe('development environment (localhost)', () => {
const devConfig: DomainConfig = {
interface EndpointScenario {
name: string;
config: DomainConfig;
expected: DerivedEndpoints;
}
const endpointScenarios: Array<EndpointScenario> = [
{
name: 'development',
config: {
base_domain: 'localhost',
public_scheme: 'http',
internal_scheme: 'http',
public_port: 8088,
internal_port: 8088,
};
const endpoints = deriveEndpointsFromDomain(devConfig);
test('derives api endpoint with port', () => {
expect(endpoints.api).toBe('http://localhost:8088/api');
});
test('derives api client endpoint with port', () => {
expect(endpoints.api_client).toBe('http://localhost:8088/api');
});
test('derives app endpoint with port', () => {
expect(endpoints.app).toBe('http://localhost:8088');
});
test('derives gateway endpoint with ws scheme', () => {
expect(endpoints.gateway).toBe('ws://localhost:8088/gateway');
});
test('derives media endpoint with port', () => {
expect(endpoints.media).toBe('http://localhost:8088/media');
});
test('derives static CDN endpoint via public origin', () => {
expect(endpoints.static_cdn).toBe('http://localhost:8088');
});
test('derives admin endpoint with port', () => {
expect(endpoints.admin).toBe('http://localhost:8088/admin');
});
test('derives marketing endpoint with port', () => {
expect(endpoints.marketing).toBe('http://localhost:8088/marketing');
});
test('derives invite endpoint with port', () => {
expect(endpoints.invite).toBe('http://localhost:8088/invite');
});
test('derives gift endpoint with port', () => {
expect(endpoints.gift).toBe('http://localhost:8088/gift');
});
});
describe('production environment (standard HTTPS port)', () => {
const prodConfig: DomainConfig = {
},
expected: {
api: 'http://localhost:8088/api',
api_client: 'http://localhost:8088/api',
app: 'http://localhost:8088',
gateway: 'ws://localhost:8088/gateway',
media: 'http://localhost:8088/media',
static_cdn: 'http://localhost:8088',
admin: 'http://localhost:8088/admin',
docs: 'https://fluxer.dev',
marketing: 'http://localhost:8088/marketing',
invite: 'http://localhost:8088/invite',
gift: 'http://localhost:8088/gift',
},
},
{
name: 'production',
config: {
base_domain: 'fluxer.app',
public_scheme: 'https',
internal_scheme: 'http',
public_port: 443,
internal_port: 8080,
};
const endpoints = deriveEndpointsFromDomain(prodConfig);
test('derives api endpoint without port', () => {
expect(endpoints.api).toBe('https://fluxer.app/api');
});
test('derives api client endpoint without port', () => {
expect(endpoints.api_client).toBe('https://fluxer.app/api');
});
test('derives app endpoint without port', () => {
expect(endpoints.app).toBe('https://fluxer.app');
});
test('derives gateway endpoint with wss scheme without port', () => {
expect(endpoints.gateway).toBe('wss://fluxer.app/gateway');
});
test('derives media endpoint without port', () => {
expect(endpoints.media).toBe('https://fluxer.app/media');
});
test('derives static CDN endpoint without port', () => {
expect(endpoints.static_cdn).toBe('https://fluxer.app');
});
test('derives admin endpoint without port', () => {
expect(endpoints.admin).toBe('https://fluxer.app/admin');
});
test('derives marketing endpoint without port', () => {
expect(endpoints.marketing).toBe('https://fluxer.app/marketing');
});
test('derives invite endpoint without port', () => {
expect(endpoints.invite).toBe('https://fluxer.app/invite');
});
test('derives gift endpoint without port', () => {
expect(endpoints.gift).toBe('https://fluxer.app/gift');
});
});
describe('staging environment (custom port)', () => {
const stagingConfig: DomainConfig = {
},
expected: {
api: 'https://fluxer.app/api',
api_client: 'https://fluxer.app/api',
app: 'https://fluxer.app',
gateway: 'wss://fluxer.app/gateway',
media: 'https://fluxer.app/media',
static_cdn: 'https://fluxer.app',
admin: 'https://fluxer.app/admin',
docs: 'https://fluxer.dev',
marketing: 'https://fluxer.app/marketing',
invite: 'https://fluxer.app/invite',
gift: 'https://fluxer.app/gift',
},
},
{
name: 'staging',
config: {
base_domain: 'staging.fluxer.dev',
public_scheme: 'https',
internal_scheme: 'http',
public_port: 8443,
internal_port: 8080,
};
const endpoints = deriveEndpointsFromDomain(stagingConfig);
test('derives api endpoint with custom port', () => {
expect(endpoints.api).toBe('https://staging.fluxer.dev:8443/api');
});
test('derives api client endpoint with custom port', () => {
expect(endpoints.api_client).toBe('https://staging.fluxer.dev:8443/api');
});
test('derives app endpoint with custom port', () => {
expect(endpoints.app).toBe('https://staging.fluxer.dev:8443');
});
test('derives gateway endpoint with wss and custom port', () => {
expect(endpoints.gateway).toBe('wss://staging.fluxer.dev:8443/gateway');
});
});
describe('custom CDN domain', () => {
const staticCdnConfig: DomainConfig = {
},
expected: {
api: 'https://staging.fluxer.dev:8443/api',
api_client: 'https://staging.fluxer.dev:8443/api',
app: 'https://staging.fluxer.dev:8443',
gateway: 'wss://staging.fluxer.dev:8443/gateway',
media: 'https://staging.fluxer.dev:8443/media',
static_cdn: 'https://staging.fluxer.dev:8443',
admin: 'https://staging.fluxer.dev:8443/admin',
docs: 'https://fluxer.dev',
marketing: 'https://staging.fluxer.dev:8443/marketing',
invite: 'https://staging.fluxer.dev:8443/invite',
gift: 'https://staging.fluxer.dev:8443/gift',
},
},
{
name: 'custom CDN',
config: {
base_domain: 'fluxer.app',
public_scheme: 'https',
internal_scheme: 'http',
public_port: 443,
static_cdn_domain: 'cdn.fluxer.app',
};
const endpoints = deriveEndpointsFromDomain(staticCdnConfig);
test('uses custom CDN domain', () => {
expect(endpoints.static_cdn).toBe('https://cdn.fluxer.app');
});
test('other endpoints use base domain', () => {
expect(endpoints.api).toBe('https://fluxer.app/api');
expect(endpoints.app).toBe('https://fluxer.app');
});
});
describe('custom invite and gift domains', () => {
const customConfig: DomainConfig = {
},
expected: {
api: 'https://fluxer.app/api',
api_client: 'https://fluxer.app/api',
app: 'https://fluxer.app',
gateway: 'wss://fluxer.app/gateway',
media: 'https://fluxer.app/media',
static_cdn: 'https://cdn.fluxer.app',
admin: 'https://fluxer.app/admin',
docs: 'https://fluxer.dev',
marketing: 'https://fluxer.app/marketing',
invite: 'https://fluxer.app/invite',
gift: 'https://fluxer.app/gift',
},
},
{
name: 'custom invite and gift domains',
config: {
base_domain: 'fluxer.app',
public_scheme: 'https',
internal_scheme: 'http',
public_port: 443,
invite_domain: 'fluxer.gg',
gift_domain: 'fluxer.gift',
};
const endpoints = deriveEndpointsFromDomain(customConfig);
test('uses custom invite domain', () => {
expect(endpoints.invite).toBe('https://fluxer.gg/invite');
});
test('uses custom gift domain', () => {
expect(endpoints.gift).toBe('https://fluxer.gift/gift');
});
test('other endpoints use base domain', () => {
expect(endpoints.api).toBe('https://fluxer.app/api');
expect(endpoints.app).toBe('https://fluxer.app');
});
});
describe('WebSocket scheme derivation', () => {
test('derives ws from http', () => {
const config: DomainConfig = {
base_domain: 'localhost',
public_scheme: 'http',
internal_scheme: 'http',
public_port: 8088,
};
const endpoints = deriveEndpointsFromDomain(config);
expect(endpoints.gateway).toBe('ws://localhost:8088/gateway');
});
test('derives wss from https', () => {
const config: DomainConfig = {
base_domain: 'fluxer.app',
public_scheme: 'https',
internal_scheme: 'http',
public_port: 443,
};
const endpoints = deriveEndpointsFromDomain(config);
expect(endpoints.gateway).toBe('wss://fluxer.app/gateway');
});
});
describe('canary environment', () => {
const canaryConfig: DomainConfig = {
},
expected: {
api: 'https://fluxer.app/api',
api_client: 'https://fluxer.app/api',
app: 'https://fluxer.app',
gateway: 'wss://fluxer.app/gateway',
media: 'https://fluxer.app/media',
static_cdn: 'https://fluxer.app',
admin: 'https://fluxer.app/admin',
docs: 'https://fluxer.dev',
marketing: 'https://fluxer.app/marketing',
invite: 'https://fluxer.gg/invite',
gift: 'https://fluxer.gift/gift',
},
},
{
name: 'canary',
config: {
base_domain: 'canary.fluxer.app',
public_scheme: 'https',
internal_scheme: 'http',
public_port: 443,
static_cdn_domain: 'cdn-canary.fluxer.app',
};
const endpoints = deriveEndpointsFromDomain(canaryConfig);
test('derives api endpoint for canary', () => {
expect(endpoints.api).toBe('https://canary.fluxer.app/api');
});
test('derives app endpoint for canary', () => {
expect(endpoints.app).toBe('https://canary.fluxer.app');
});
test('derives gateway endpoint for canary', () => {
expect(endpoints.gateway).toBe('wss://canary.fluxer.app/gateway');
});
test('uses custom CDN domain for canary', () => {
expect(endpoints.static_cdn).toBe('https://cdn-canary.fluxer.app');
});
});
describe('edge cases', () => {
test('handles standard HTTP port (80)', () => {
const config: DomainConfig = {
base_domain: 'example.com',
public_scheme: 'http',
internal_scheme: 'http',
public_port: 80,
};
const endpoints = deriveEndpointsFromDomain(config);
expect(endpoints.api).toBe('http://example.com/api');
expect(endpoints.gateway).toBe('ws://example.com/gateway');
});
test('handles ports when undefined', () => {
const config: DomainConfig = {
base_domain: 'example.com',
public_scheme: 'https',
internal_scheme: 'http',
};
const endpoints = deriveEndpointsFromDomain(config);
expect(endpoints.api).toBe('https://example.com/api');
expect(endpoints.app).toBe('https://example.com');
});
test('handles IPv4 addresses', () => {
const config: DomainConfig = {
base_domain: '127.0.0.1',
public_scheme: 'http',
internal_scheme: 'http',
public_port: 8088,
};
const endpoints = deriveEndpointsFromDomain(config);
expect(endpoints.api).toBe('http://127.0.0.1:8088/api');
});
},
expected: {
api: 'https://canary.fluxer.app/api',
api_client: 'https://canary.fluxer.app/api',
app: 'https://canary.fluxer.app',
gateway: 'wss://canary.fluxer.app/gateway',
media: 'https://canary.fluxer.app/media',
static_cdn: 'https://cdn-canary.fluxer.app',
admin: 'https://canary.fluxer.app/admin',
docs: 'https://fluxer.dev',
marketing: 'https://canary.fluxer.app/marketing',
invite: 'https://canary.fluxer.app/invite',
gift: 'https://canary.fluxer.app/gift',
},
},
{
name: 'standard HTTP port',
config: {
base_domain: 'example.com',
public_scheme: 'http',
internal_scheme: 'http',
public_port: 80,
},
expected: {
api: 'http://example.com/api',
api_client: 'http://example.com/api',
app: 'http://example.com',
gateway: 'ws://example.com/gateway',
media: 'http://example.com/media',
static_cdn: 'http://example.com',
admin: 'http://example.com/admin',
docs: 'https://fluxer.dev',
marketing: 'http://example.com/marketing',
invite: 'http://example.com/invite',
gift: 'http://example.com/gift',
},
},
{
name: 'unspecified port',
config: {
base_domain: 'example.com',
public_scheme: 'https',
internal_scheme: 'http',
},
expected: {
api: 'https://example.com/api',
api_client: 'https://example.com/api',
app: 'https://example.com',
gateway: 'wss://example.com/gateway',
media: 'https://example.com/media',
static_cdn: 'https://example.com',
admin: 'https://example.com/admin',
docs: 'https://fluxer.dev',
marketing: 'https://example.com/marketing',
invite: 'https://example.com/invite',
gift: 'https://example.com/gift',
},
},
{
name: 'IPv4',
config: {
base_domain: '127.0.0.1',
public_scheme: 'http',
internal_scheme: 'http',
public_port: 8088,
},
expected: {
api: 'http://127.0.0.1:8088/api',
api_client: 'http://127.0.0.1:8088/api',
app: 'http://127.0.0.1:8088',
gateway: 'ws://127.0.0.1:8088/gateway',
media: 'http://127.0.0.1:8088/media',
static_cdn: 'http://127.0.0.1:8088',
admin: 'http://127.0.0.1:8088/admin',
docs: 'https://fluxer.dev',
marketing: 'http://127.0.0.1:8088/marketing',
invite: 'http://127.0.0.1:8088/invite',
gift: 'http://127.0.0.1:8088/gift',
},
},
{
name: 'external CDN uses HTTPS without the public port',
config: {
base_domain: 'localhost',
public_scheme: 'http',
internal_scheme: 'http',
public_port: 8088,
static_cdn_domain: 'cdn.example.com',
},
expected: {
api: 'http://localhost:8088/api',
api_client: 'http://localhost:8088/api',
app: 'http://localhost:8088',
gateway: 'ws://localhost:8088/gateway',
media: 'http://localhost:8088/media',
static_cdn: 'https://cdn.example.com',
admin: 'http://localhost:8088/admin',
docs: 'https://fluxer.dev',
marketing: 'http://localhost:8088/marketing',
invite: 'http://localhost:8088/invite',
gift: 'http://localhost:8088/gift',
},
},
];
describe('deriveEndpointsFromDomain', () => {
test.each(endpointScenarios)('$name', ({config, expected}) => {
expect(deriveEndpointsFromDomain(config)).toEqual(expected);
});
});
@@ -433,10 +430,9 @@ describe('parsePublicOrigin', () => {
});
test('normalizes an explicitly written standard port to the portless form', () => {
const origin = parsePublicOrigin('https://chat.example.com:443');
assert.ok(origin);
expect(origin).toEqual({public_scheme: 'https', base_domain: 'chat.example.com', public_port: 443});
expect(buildUrl(origin?.public_scheme ?? 'https', origin?.base_domain ?? '', origin?.public_port)).toBe(
'https://chat.example.com',
);
expect(buildUrl(origin.public_scheme, origin.base_domain, origin.public_port)).toBe('https://chat.example.com');
expect(parsePublicOrigin('http://chat.example.com:80')?.public_port).toBe(80);
});
test('canonicalizes the host', () => {
@@ -472,11 +468,10 @@ describe('parsePublicOrigin', () => {
describe('endpoints derived from a public origin', () => {
test('an origin with a non-standard port ports every derived endpoint', () => {
const origin = parsePublicOrigin('https://chat.example.com:29080');
assert.ok(origin);
const endpoints = deriveEndpointsFromDomain({
base_domain: origin?.base_domain ?? '',
public_scheme: origin?.public_scheme ?? 'https',
...origin,
internal_scheme: 'http',
public_port: origin?.public_port,
});
expect(endpoints.api_client).toBe('https://chat.example.com:29080/api');
expect(endpoints.app).toBe('https://chat.example.com:29080');
@@ -485,11 +480,10 @@ describe('endpoints derived from a public origin', () => {
});
test('an origin written with an explicit :443 derives portless endpoints', () => {
const origin = parsePublicOrigin('https://chat.example.com:443');
assert.ok(origin);
const endpoints = deriveEndpointsFromDomain({
base_domain: origin?.base_domain ?? '',
public_scheme: origin?.public_scheme ?? 'https',
...origin,
internal_scheme: 'http',
public_port: origin?.public_port,
});
expect(endpoints.admin).toBe('https://chat.example.com/admin');
expect(endpoints.app).toBe('https://chat.example.com');
@@ -1,17 +1,11 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {readFileSync} from 'node:fs';
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import {loadConfig, resetConfig} from '@fluxer/config/src/ConfigLoader';
import {normalizePublicEndpoint} from '@fluxer/config/src/EndpointDerivation';
import type {MasterConfig} from '@fluxer/config/src/MasterConfig';
import {afterEach, beforeEach, describe, expect, test, vi} from 'vitest';
const SELF_HOSTING = path.join(fileURLToPath(new URL('../../../../', import.meta.url)), 'deploy/self-hosting');
const compose = readFileSync(path.join(SELF_HOSTING, 'docker-compose.yml'), 'utf8');
const lines = compose.split('\n');
import {serviceEnvironment, serviceList, serviceNames} from './SelfHostingCompose';
const DOMAIN = 'chat.example.com';
const PUBLIC_PORT = '19080';
@@ -55,97 +49,8 @@ const ORIGIN_ONLY_ENV: Record<string, string> = {
FLUXER_HTTPS_PORT: ORIGIN_PORT,
};
const indentOf = (line: string): number => line.length - line.trimStart().length;
function unquote(value: string): string {
const trimmed = value.trim();
const quote = trimmed.slice(0, 1);
const quoted = (quote === '"' || quote === "'") && trimmed.length > 1 && trimmed.endsWith(quote);
return quoted ? trimmed.slice(1, -1) : trimmed;
}
function anchorStart(anchor: string): number {
const start = lines.findIndex((line) => line.startsWith('x-') && line.endsWith(`&${anchor}`));
if (start === -1) {
throw new Error(`docker-compose.yml has no &${anchor} anchor`);
}
return start;
}
function mapping(start: number, indent: number): Map<string, string> {
const entries = new Map<string, string>();
for (let index = start; index < lines.length; index += 1) {
const line = lines[index];
if (line.trim().length === 0 || line.trimStart().startsWith('#')) {
continue;
}
if (indentOf(line) < indent) {
break;
}
if (indentOf(line) > indent) {
continue;
}
const merge = /^<<: \*([a-z][a-z0-9-]*)$/u.exec(line.trim());
if (merge) {
for (const [key, value] of mapping(anchorStart(merge[1]) + 1, 2)) {
entries.set(key, value);
}
continue;
}
const entry = /^([A-Za-z_][A-Za-z0-9_]*): (.*)$/u.exec(line.trim());
if (entry) {
entries.set(entry[1], unquote(entry[2]));
}
}
return entries;
}
function serviceSection(service: string, key: string): number {
const start = lines.indexOf(` ${service}:`);
if (start === -1) {
throw new Error(`docker-compose.yml has no ${service} service`);
}
for (let index = start + 1; index < lines.length; index += 1) {
const line = lines[index];
if (line.trim().length > 0 && indentOf(line) <= 2) {
break;
}
if (line === ` ${key}:`) {
return index;
}
}
return -1;
}
function serviceList(service: string, key: string): Array<string> {
const start = serviceSection(service, key);
if (start === -1) {
return [];
}
const items: Array<string> = [];
for (let index = start + 1; index < lines.length; index += 1) {
const line = lines[index];
if (line.trim().length === 0) {
continue;
}
if (indentOf(line) <= 4) {
break;
}
const item = /^- (.*)$/u.exec(line.trim());
if (item) {
items.push(unquote(item[1]));
}
}
return items;
}
const SERVICES_WITHOUT_ENDPOINT_REPAIR = new Set(['edge']);
function serviceNames(): Array<string> {
const section = compose.slice(compose.indexOf('\nservices:\n'), compose.indexOf('\nnetworks:\n'));
return [...section.matchAll(/\n {2}([a-z][a-z0-9_-]*):\n/gu)].map(([, name]) => name);
}
function closingBrace(value: string, open: number): number {
let depth = 0;
for (let index = open; index < value.length; index += 1) {
@@ -196,7 +101,7 @@ function expand(value: string, env: Record<string, string>): string {
function expandedEnvironment(service: string, env: Record<string, string>): Record<string, string> {
const expanded: Record<string, string> = {};
for (const [key, value] of mapping(serviceSection(service, 'environment') + 1, 6)) {
for (const [key, value] of Object.entries(serviceEnvironment(service))) {
expanded[key] = expand(value, env);
}
return expanded;
@@ -257,7 +162,7 @@ function browserFacingUrls(config: MasterConfig): Array<[string, string]> {
return entries.filter(([, value]) => publicUrl(value) !== null);
}
async function loadApiConfig(env: Record<string, string>): Promise<MasterConfig | Error> {
async function loadApiConfig(env: Record<string, string>): Promise<MasterConfig> {
for (const key of Object.keys(process.env)) {
if (key.startsWith('FLUXER_')) {
vi.stubEnv(key, undefined);
@@ -268,16 +173,31 @@ async function loadApiConfig(env: Record<string, string>): Promise<MasterConfig
vi.stubEnv(key, value);
}
}
try {
return await loadConfig();
} catch (error) {
return error instanceof Error ? error : new Error(String(error));
}
return loadConfig();
}
describe('Compose environment interpolation', () => {
test.each([
[`\${VALUE}`, {VALUE: 'set'}, 'set'],
['$VALUE/path', {VALUE: 'set'}, 'set/path'],
[`\${VALUE:-fallback}`, {VALUE: ''}, 'fallback'],
[`\${VALUE:-\${OTHER:-fallback}}`, {OTHER: 'nested'}, 'nested'],
[`\${VALUE:+alternate}`, {VALUE: 'set'}, 'alternate'],
[`\${VALUE:+alternate}`, {}, ''],
[`\${VALUE:?required}`, {VALUE: 'set'}, 'set'],
] as const)('expands %s with %j to %s', (source, environment, expected) => {
expect(expand(source, environment)).toBe(expected);
});
test('rejects a missing required value and unbalanced interpolation', () => {
expect(() => expand(`\${VALUE:?required}`, {})).toThrow('VALUE is required: required');
expect(() => expand('${VALUE:-fallback', {})).toThrow('unbalanced interpolation');
});
});
describe('the shipped compose stack expanded on a non-default port', () => {
test('every service handed a public URL is handed the base domain and port that repair it', () => {
const starved = serviceNames()
const starved = serviceNames
.filter((service) => !SERVICES_WITHOUT_ENDPOINT_REPAIR.has(service))
.filter((service) => {
const environment = expandedEnvironment(service, PORT_ONLY_ENV);
@@ -289,9 +209,10 @@ describe('the shipped compose stack expanded on a non-default port', () => {
});
test('every public URL the stack hands a browser carries the port', () => {
const entries = serviceNames()
const entries = serviceNames
.filter((service) => !SERVICES_WITHOUT_ENDPOINT_REPAIR.has(service))
.flatMap((service) => repairedPublicUrls(service, PORT_ONLY_ENV));
expect(entries.length).toBeGreaterThan(0);
expect(withoutPort(entries, PUBLIC_PORT)).toEqual([]);
});
@@ -319,23 +240,17 @@ describe('a public origin carrying a port while FLUXER_PUBLIC_PORT stays standar
test('the compose overrides all carry the origin port', () => {
const environment = expandedEnvironment('api', ORIGIN_ONLY_ENV);
const entries = publicUrlNames(environment).map((name): [string, string] => [name, environment[name]]);
expect(entries.length).toBeGreaterThan(0);
expect(withoutPort(entries, ORIGIN_PORT)).toEqual([]);
});
test('the loaded config either ports every public URL or refuses the split', async () => {
test('the loaded config ports every public URL using the explicit origin', async () => {
const loaded = await loadApiConfig(ORIGIN_ONLY_ENV);
if (loaded instanceof Error) {
expect(loaded.message).not.toBe('');
return;
}
expect(withoutPort(browserFacingUrls(loaded), ORIGIN_PORT)).toEqual([]);
});
test('the port-only recipe ports every public URL the loaded config exposes', async () => {
const loaded = await loadApiConfig(PORT_ONLY_ENV);
if (loaded instanceof Error) {
throw loaded;
}
expect(withoutPort(browserFacingUrls(loaded), PUBLIC_PORT)).toEqual([]);
});
});
@@ -0,0 +1,55 @@
import assert from 'node:assert/strict';
import {readFileSync} from 'node:fs';
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import {type ConfigObject, isConfigObject} from '@fluxer/config/src/config_loader/ConfigObject';
import {parse} from 'yaml';
const SELF_HOSTING = fileURLToPath(new URL('../../../../deploy/self-hosting/', import.meta.url));
function readSelfHostingFile(name: string): string {
return readFileSync(path.join(SELF_HOSTING, name), 'utf8');
}
function mapping(value: unknown, name: string): ConfigObject {
assert.ok(isConfigObject(value), `${name} must be a mapping`);
return value;
}
const compose = mapping(parse(readSelfHostingFile('docker-compose.yml'), {merge: true}), 'docker-compose.yml');
const services = mapping(compose.services, 'services');
export const serviceNames = Object.keys(services);
assert.ok(serviceNames.length > 0, 'docker-compose.yml must define services');
export function composeService(name: string): ConfigObject {
return mapping(services[name], `services.${name}`);
}
function environment(value: unknown, name: string): Record<string, string> {
return Object.fromEntries(
Object.entries(mapping(value, name)).map(([key, entry]) => {
assert.ok(
typeof entry === 'string' || typeof entry === 'number' || typeof entry === 'boolean',
`${name}.${key} must have an explicit scalar value`,
);
return [key, String(entry)];
}),
);
}
export function serviceEnvironment(name: string): Record<string, string> {
const value = composeService(name).environment;
return value === undefined ? {} : environment(value, `services.${name}.environment`);
}
export const sharedEnvironment = environment(compose['x-fluxer-env'], 'x-fluxer-env');
export function serviceList(name: string, key: string): Array<string> {
const value = composeService(name)[key];
assert.ok(Array.isArray(value), `services.${name}.${key} must be a list`);
assert.ok(
value.every((item) => typeof item === 'string'),
`services.${name}.${key} must contain strings`,
);
return value;
}
@@ -1,38 +1,28 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {readFileSync} from 'node:fs';
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import {describe, expect, test} from 'vitest';
const SELF_HOSTING = path.join(fileURLToPath(new URL('../../../../', import.meta.url)), 'deploy/self-hosting');
const compose = readFileSync(path.join(SELF_HOSTING, 'docker-compose.yml'), 'utf8');
const serviceBlock = (name: string): string => {
const start = compose.indexOf(`\n ${name}:\n`);
if (start === -1) {
throw new Error(`docker-compose.yml has no ${name} service`);
}
const rest = compose.slice(start + 1);
const next = rest.slice(1).search(/\n {2}[a-z][a-z0-9_-]*:\n/u);
return next === -1 ? rest : rest.slice(0, next + 1);
};
const sharedEnv = compose.slice(compose.indexOf('x-fluxer-env: &fluxer-env'), compose.indexOf('\nx-fluxer-service:'));
import {serviceEnvironment, serviceNames, sharedEnvironment} from './SelfHostingCompose';
describe('the shipped compose stack wires every service it starts', () => {
test('no service sizes a pool for a connection it cannot make', () => {
for (const [, name] of compose.matchAll(/\n {2}([a-z][a-z0-9_-]*):\n/gu)) {
const block = serviceBlock(name);
if (block.includes('FLUXER_POSTGRES_MAX_CONNECTIONS')) {
expect(block).toMatch(/<<: \*fluxer-(postgres-)?env/u);
}
const pooledServices = serviceNames.filter((name) => 'FLUXER_POSTGRES_MAX_CONNECTIONS' in serviceEnvironment(name));
expect(pooledServices.length).toBeGreaterThan(0);
for (const name of pooledServices) {
expect(serviceEnvironment(name), name).toMatchObject({
FLUXER_DATABASE_BACKEND: 'postgres',
FLUXER_POSTGRES_HOST: expect.stringMatching(/\S/u),
FLUXER_POSTGRES_PORT: expect.stringMatching(/\S/u),
FLUXER_POSTGRES_DATABASE: expect.stringMatching(/\S/u),
FLUXER_POSTGRES_USERNAME: expect.stringMatching(/\S/u),
FLUXER_POSTGRES_PASSWORD: expect.stringMatching(/\S/u),
});
}
});
test('the shared block sets the client-IP trust the merged services read', () => {
expect(sharedEnv).toContain('FLUXER_TRUST_CLIENT_IP_HEADER: "${FLUXER_TRUST_CLIENT_IP_HEADER:-true}"');
expect(sharedEnv).toContain('FLUXER_CLIENT_IP_HEADER_NAME: ${FLUXER_CLIENT_IP_HEADER_NAME:-x-forwarded-for}');
expect(sharedEnvironment).toMatchObject({
FLUXER_TRUST_CLIENT_IP_HEADER: `\${FLUXER_TRUST_CLIENT_IP_HEADER:-true}`,
FLUXER_CLIENT_IP_HEADER_NAME: `\${FLUXER_CLIENT_IP_HEADER_NAME:-x-forwarded-for}`,
});
});
});
@@ -1,28 +1,15 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {readFileSync} from 'node:fs';
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import {describe, expect, test} from 'vitest';
const SELF_HOSTING = path.join(fileURLToPath(new URL('../../../../', import.meta.url)), 'deploy/self-hosting');
const compose = readFileSync(path.join(SELF_HOSTING, 'docker-compose.yml'), 'utf8');
const serviceBlock = (name: string): string => {
const start = compose.indexOf(`\n ${name}:\n`);
if (start === -1) {
throw new Error(`docker-compose.yml has no ${name} service`);
}
const rest = compose.slice(start + 1);
const next = rest.slice(1).search(/\n {2}[a-z][a-z0-9_-]*:\n/u);
return next === -1 ? rest : rest.slice(0, next + 1);
};
import {composeService, serviceEnvironment, serviceList} from './SelfHostingCompose';
describe('the shipped object store checks the credentials the stack sends', () => {
const init = serviceBlock('seaweedfs-init');
const entrypoint = serviceList('seaweedfs-init', 'entrypoint');
const init = entrypoint[2];
test('seaweedfs-init applies an S3 identity built from the .env credentials', () => {
expect(entrypoint.slice(0, 2)).toEqual(['/bin/sh', '-c']);
expect(entrypoint).toHaveLength(3);
expect(init).toContain(
's3.configure -user=fluxer -access_key=$$FLUXER_S3_ACCESS_KEY -secret_key=$$FLUXER_S3_SECRET_KEY',
);
@@ -31,7 +18,7 @@ describe('the shipped object store checks the credentials the stack sends', () =
test('seaweedfs-init is handed the same credentials the api requires', () => {
for (const name of ['FLUXER_S3_ACCESS_KEY', 'FLUXER_S3_SECRET_KEY']) {
expect(init).toMatch(new RegExp(`${name}: \\$\\{${name}:\\?set ${name} in \\.env\\}`, 'u'));
expect(serviceEnvironment('seaweedfs-init')[name]).toBe(`\${${name}:?set ${name} in .env}`);
}
});
@@ -40,17 +27,20 @@ describe('the shipped object store checks the credentials the stack sends', () =
const ready = init.indexOf('echo "buckets ready"');
expect(configure).toBeGreaterThan(-1);
expect(ready).toBeGreaterThan(configure);
expect(init).toContain('echo "seaweedfs-init could not configure the S3 identity" >&2');
expect(init).toContain('exit 1');
expect(init).toMatch(
/if ! echo "s3\.configure [^"]+"[^;]+; then\s+echo "seaweedfs-init could not configure the S3 identity" >&2;\s+exit 1;\s+fi;/u,
);
});
test('media-proxy signs its reads, which the store now refuses to serve unsigned', () => {
expect(serviceBlock('media-proxy')).toContain('FLUXER_S3_READ_SIGNED: "true"');
expect(serviceEnvironment('media-proxy').FLUXER_S3_READ_SIGNED).toBe('true');
});
test('every service that reaches the store waits for the identity to exist', () => {
for (const name of ['api', 'worker', 'media-proxy']) {
expect(serviceBlock(name)).toContain('seaweedfs-init: {condition: service_completed_successfully}');
expect(composeService(name).depends_on, name).toMatchObject({
'seaweedfs-init': {condition: 'service_completed_successfully'},
});
}
});
});
@@ -0,0 +1,5 @@
export type ConfigObject = Record<string, unknown>;
export function isConfigObject(value: unknown): value is ConfigObject {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {type ConfigObject, isConfigObject} from '@fluxer/config/src/config_loader/ConfigObject';
type ConfigPathKey = string | number;
type ConfigObject = Record<string, unknown>;
type ConfigContainer = ConfigObject | Array<unknown>;
type EnvValueParser = (raw: string) => unknown;
@@ -436,12 +437,8 @@ const NAMED_FLUXER_ENV_OVERRIDES: Record<string, NamedEnvOverride> = {
FLUXER_GEOIP_DB_PATH: {path: ['geoip', 'maxmind_db_path']},
};
function isPlainObject(value: unknown): value is ConfigObject {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
function isContainer(value: unknown): value is ConfigContainer {
return isPlainObject(value) || Array.isArray(value);
return isConfigObject(value) || Array.isArray(value);
}
function createChildContainer(nextKey: ConfigPathKey | undefined): ConfigContainer {
+1 -10
View File
@@ -1,18 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import tsconfigPaths from 'vite-tsconfig-paths';
import {defineConfig} from 'vitest/config';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default defineConfig({
plugins: [
tsconfigPaths({
root: path.resolve(__dirname, '../..'),
}),
],
resolve: {tsconfigPaths: true},
test: {
globals: true,
environment: 'node',
-1
View File
@@ -13,7 +13,6 @@
},
"devDependencies": {
"@typescript/native-preview": "catalog:",
"vite-tsconfig-paths": "catalog:",
"vitest": "catalog:"
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"ageRestrictedGeos": [{"countryCode": "GB", "regionCode": null}, {"countryCode": "BR", "regionCode": null}],
"ageBlockedGeos": [{"countryCode": "US", "regionCode": "MS"}]
}
@@ -42,7 +42,6 @@ export const ValidationErrorCodes = {
CHANNEL_ID_IS_REQUIRED: 'CHANNEL_ID_IS_REQUIRED',
CHANNEL_MUST_BE_DM_OR_GROUP_DM: 'CHANNEL_MUST_BE_DM_OR_GROUP_DM',
CHANNEL_MUST_BE_VOICE: 'CHANNEL_MUST_BE_VOICE',
CHANNEL_NAME_EMPTY_AFTER_NORMALIZATION: 'CHANNEL_NAME_EMPTY_AFTER_NORMALIZATION',
CHANNEL_NOT_FOUND: 'CHANNEL_NOT_FOUND',
COLOR_VALUE_TOO_HIGH: 'COLOR_VALUE_TOO_HIGH',
COLOR_VALUE_TOO_LOW: 'COLOR_VALUE_TOO_LOW',
@@ -85,8 +84,6 @@ export const ValidationErrorCodes = {
FAVORITE_MEME_NOT_FOUND: 'FAVORITE_MEME_NOT_FOUND',
FILE_INDEX_EXCEEDS_MAXIMUM: 'FILE_INDEX_EXCEEDS_MAXIMUM',
FILE_NOT_FOUND: 'FILE_NOT_FOUND',
FILENAME_EMPTY_AFTER_NORMALIZATION: 'FILENAME_EMPTY_AFTER_NORMALIZATION',
FILENAME_INVALID_CHARACTERS: 'FILENAME_INVALID_CHARACTERS',
FILENAME_LENGTH_INVALID: 'FILENAME_LENGTH_INVALID',
FORWARD_MESSAGES_CANNOT_CONTAIN_CONTENT: 'FORWARD_MESSAGES_CANNOT_CONTAIN_CONTENT',
FORWARD_REFERENCE_REQUIRES_CHANNEL_AND_MESSAGE: 'FORWARD_REFERENCE_REQUIRES_CHANNEL_AND_MESSAGE',
@@ -243,3 +240,9 @@ export const ValidationErrorCodes = {
} as const;
export type ValidationErrorCode = ValueOf<typeof ValidationErrorCodes>;
const validationErrorCodeSet = new Set<string>(Object.values(ValidationErrorCodes));
export function isValidationErrorCode(value: string): value is ValidationErrorCode {
return validationErrorCodeSet.has(value);
}
+1 -10
View File
@@ -1,18 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import tsconfigPaths from 'vite-tsconfig-paths';
import {defineConfig} from 'vitest/config';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default defineConfig({
plugins: [
tsconfigPaths({
root: path.resolve(__dirname, '../..'),
}),
],
resolve: {tsconfigPaths: true},
test: {
globals: true,
environment: 'node',
+1 -1
View File
@@ -17,13 +17,13 @@
"@fluxer/hono_types": "workspace:*",
"@fluxer/i18n": "workspace:*",
"@fluxer/logger": "workspace:*",
"@fluxer/schema": "workspace:*",
"hono": "catalog:"
},
"devDependencies": {
"@messageformat/parser": "catalog:",
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"vite-tsconfig-paths": "catalog:",
"vitest": "catalog:"
}
}
+8 -14
View File
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {ErrorResponse} from '@fluxer/schema/src/domains/common/ErrorSchemas';
import {HTTPException} from 'hono/http-exception';
export type FluxerErrorData = Record<string, unknown>;
@@ -36,23 +37,16 @@ export class FluxerError extends HTTPException {
}
override getResponse(): Response {
return new Response(
JSON.stringify({
code: this.code,
message: this.message,
...this.data,
}),
{
status: this.status,
headers: {
'Content-Type': 'application/json',
...this.headers,
},
return new Response(JSON.stringify(this.toJSON()), {
status: this.status,
headers: {
'Content-Type': 'application/json',
...this.headers,
},
);
});
}
toJSON(): Record<string, unknown> {
toJSON(): ErrorResponse {
return {
code: this.code,
message: this.message,
+12 -36
View File
@@ -15,12 +15,10 @@ interface HttpErrorOptions {
export class BadRequestError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
...options,
code: options.code ?? APIErrorCodes.BAD_REQUEST,
message: options.message ?? 'Bad Request',
status: HttpStatus.BAD_REQUEST,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'BadRequestError';
}
@@ -29,12 +27,10 @@ export class BadRequestError extends FluxerError {
export class UnauthorizedError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
...options,
code: options.code ?? APIErrorCodes.UNAUTHORIZED,
message: options.message ?? 'Unauthorized',
status: HttpStatus.UNAUTHORIZED,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'UnauthorizedError';
}
@@ -43,12 +39,10 @@ export class UnauthorizedError extends FluxerError {
export class ForbiddenError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
...options,
code: options.code ?? APIErrorCodes.FORBIDDEN,
message: options.message ?? 'Forbidden',
status: HttpStatus.FORBIDDEN,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'ForbiddenError';
}
@@ -57,12 +51,10 @@ export class ForbiddenError extends FluxerError {
export class NotFoundError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
...options,
code: options.code ?? APIErrorCodes.NOT_FOUND,
message: options.message ?? 'Not Found',
status: HttpStatus.NOT_FOUND,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'NotFoundError';
}
@@ -71,12 +63,10 @@ export class NotFoundError extends FluxerError {
export class MethodNotAllowedError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
...options,
code: options.code ?? APIErrorCodes.METHOD_NOT_ALLOWED,
message: options.message ?? 'Method Not Allowed',
status: HttpStatus.METHOD_NOT_ALLOWED,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'MethodNotAllowedError';
}
@@ -85,12 +75,10 @@ export class MethodNotAllowedError extends FluxerError {
export class ConflictError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
...options,
code: options.code ?? APIErrorCodes.CONFLICT,
message: options.message ?? 'Conflict',
status: HttpStatus.CONFLICT,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'ConflictError';
}
@@ -99,12 +87,10 @@ export class ConflictError extends FluxerError {
export class GoneError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
...options,
code: options.code ?? APIErrorCodes.GONE,
message: options.message ?? 'Gone',
status: HttpStatus.GONE,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'GoneError';
}
@@ -113,12 +99,10 @@ export class GoneError extends FluxerError {
export class InternalServerError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
...options,
code: options.code ?? APIErrorCodes.INTERNAL_SERVER_ERROR,
message: options.message ?? 'Internal Server Error',
status: HttpStatus.INTERNAL_SERVER_ERROR,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'InternalServerError';
}
@@ -127,12 +111,10 @@ export class InternalServerError extends FluxerError {
export class NotImplementedError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
...options,
code: options.code ?? APIErrorCodes.NOT_IMPLEMENTED,
message: options.message ?? 'Not Implemented',
status: HttpStatus.NOT_IMPLEMENTED,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'NotImplementedError';
}
@@ -141,12 +123,10 @@ export class NotImplementedError extends FluxerError {
export class ServiceUnavailableError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
...options,
code: options.code ?? APIErrorCodes.SERVICE_UNAVAILABLE,
message: options.message ?? 'Service Unavailable',
status: HttpStatus.SERVICE_UNAVAILABLE,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'ServiceUnavailableError';
}
@@ -155,12 +135,10 @@ export class ServiceUnavailableError extends FluxerError {
export class BadGatewayError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
...options,
code: options.code ?? APIErrorCodes.BAD_GATEWAY,
message: options.message ?? 'Bad Gateway',
status: HttpStatus.BAD_GATEWAY,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'BadGatewayError';
}
@@ -169,12 +147,10 @@ export class BadGatewayError extends FluxerError {
export class GatewayTimeoutError extends FluxerError {
constructor(options: HttpErrorOptions = {}) {
super({
...options,
code: options.code ?? APIErrorCodes.GATEWAY_TIMEOUT,
message: options.message ?? 'Gateway Timeout',
status: HttpStatus.GATEWAY_TIMEOUT,
data: options.data,
headers: options.headers,
cause: options.cause,
});
this.name = 'GatewayTimeoutError';
}
+2 -3
View File
@@ -3,11 +3,10 @@
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {HttpStatus} from '@fluxer/constants/src/HttpConstants';
import {FluxerError} from '@fluxer/errors/src/FluxerError';
import type {ValidationErrorItem} from '@fluxer/schema/src/domains/common/ErrorSchemas';
interface PathError {
path: string;
interface PathError extends ValidationErrorItem {
code: string;
message: string;
}
interface ValidationErrorOptions {
@@ -8,110 +8,84 @@ import {getErrorMessage} from '@fluxer/errors/src/i18n/ErrorI18n';
import type {BaseHonoEnv} from '@fluxer/hono_types/src/HonoTypes';
import {Logger} from '@fluxer/logger/src/Logger';
import {Hono} from 'hono';
import {describe, expect, it, vi} from 'vitest';
import {afterEach, describe, expect, it, vi} from 'vitest';
interface ErrorResponse {
code: string;
message: string;
}
afterEach(() => {
vi.restoreAllMocks();
});
function createApp(): Hono<BaseHonoEnv> {
function createApp(error: Error, requestLocale?: string, requestId?: string): Hono<BaseHonoEnv> {
const app = new Hono<BaseHonoEnv>();
app.onError(AppErrorHandler);
app.use('*', async (ctx, next) => {
ctx.set('requestLocale', requestLocale);
ctx.set('requestId', requestId);
await next();
});
app.get('/test', () => {
throw error;
});
return app;
}
describe('AppErrorHandler i18n fallbacks', () => {
it('localizes unexpected errors from Accept-Language when middleware locale is missing', async () => {
const errorLoggerSpy = vi.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
const error = new Error('boom');
const app = createApp();
app.get('/test', () => {
throw error;
});
try {
const response = await app.request('/test', {
headers: {
'accept-language': 'fr-CA,fr;q=0.9,en;q=0.8',
},
});
expect(response.status).toBe(500);
const body = (await response.json()) as ErrorResponse;
expect(body.code).toBe(APIErrorCodes.INTERNAL_SERVER_ERROR);
expect(body.message).toBe('Erreur interne du serveur.');
expect(errorLoggerSpy).toHaveBeenCalledTimes(1);
expect(errorLoggerSpy).toHaveBeenCalledWith(
{err: error, status: 500, method: 'GET', path: '/test', requestId: undefined},
'Unhandled error occurred',
);
} finally {
errorLoggerSpy.mockRestore();
it.each([
{
name: 'uses Accept-Language when middleware locale is missing',
acceptLanguage: 'fr-CA,fr;q=0.9,en;q=0.8',
requestLocale: undefined,
requestId: undefined,
message: 'Erreur interne du serveur.',
},
{
name: 'prefers the middleware locale over Accept-Language',
acceptLanguage: 'fr',
requestLocale: Locales.EN_US,
requestId: 'request-123',
message: 'Internal server error.',
},
{
name: 'defaults to English without a locale or language header',
acceptLanguage: undefined,
requestLocale: undefined,
requestId: undefined,
message: 'Internal server error.',
},
])('$name and logs unexpected errors with request metadata', async ({
acceptLanguage,
requestLocale,
requestId,
message,
}) => {
const errorLogger = vi.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
const error = new Error('Private internal failure');
const headers = new Headers();
if (acceptLanguage !== undefined) {
headers.set('accept-language', acceptLanguage);
}
const response = await createApp(error, requestLocale, requestId).request('/test', {headers});
expect(response.status).toBe(500);
expect(await response.json()).toEqual({code: APIErrorCodes.INTERNAL_SERVER_ERROR, message});
expect(errorLogger).toHaveBeenCalledExactlyOnceWith(
{err: error, status: 500, method: 'GET', path: '/test', requestId},
'Unhandled error occurred',
);
});
it('localizes FluxerError responses without errorI18nService in context', async () => {
const app = createApp();
app.get('/test', () => {
throw new BadRequestError({code: APIErrorCodes.BAD_REQUEST});
});
const response = await app.request('/test', {
headers: {
'accept-language': 'fr',
},
});
it('localizes FluxerError responses without an i18n service and logs them as expected rejections', async () => {
const errorLogger = vi.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
const debugLogger = vi.spyOn(Logger.prototype, 'debug').mockImplementation(() => {});
const error = new BadRequestError({code: APIErrorCodes.BAD_REQUEST});
const response = await createApp(error).request('/test', {headers: {'accept-language': 'fr'}});
expect(response.status).toBe(400);
const body = (await response.json()) as ErrorResponse;
expect(body.code).toBe(APIErrorCodes.BAD_REQUEST);
expect(body.message).toBe(getErrorMessage('http.bad_request', 'fr'));
});
it('prefers requestLocale context over Accept-Language header', async () => {
const errorLoggerSpy = vi.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
const error = new Error('boom');
const app = createApp();
app.use('*', async (ctx, next) => {
ctx.set('requestLocale', Locales.EN_US);
await next();
expect(await response.json()).toEqual({
code: APIErrorCodes.BAD_REQUEST,
message: getErrorMessage('http.bad_request', 'fr'),
});
app.get('/test', () => {
throw error;
});
try {
const response = await app.request('/test', {
headers: {
'accept-language': 'fr',
},
});
expect(response.status).toBe(500);
const body = (await response.json()) as ErrorResponse;
expect(body.code).toBe(APIErrorCodes.INTERNAL_SERVER_ERROR);
expect(body.message).toBe('Internal server error.');
expect(errorLoggerSpy).toHaveBeenCalledTimes(1);
expect(errorLoggerSpy).toHaveBeenCalledWith(
{err: error, status: 500, method: 'GET', path: '/test', requestId: undefined},
'Unhandled error occurred',
);
} finally {
errorLoggerSpy.mockRestore();
}
});
it('returns 500 for unexpected errors with request metadata', async () => {
const errorLoggerSpy = vi.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
const error = new Error('boom');
const app = createApp();
app.get('/test', () => {
throw error;
});
try {
const response = await app.request('/test');
expect(response.status).toBe(500);
const body = (await response.json()) as ErrorResponse;
expect(body.code).toBe(APIErrorCodes.INTERNAL_SERVER_ERROR);
expect(errorLoggerSpy).toHaveBeenCalledTimes(1);
expect(errorLoggerSpy).toHaveBeenCalledWith(
{err: error, status: 500, method: 'GET', path: '/test', requestId: undefined},
'Unhandled error occurred',
);
} finally {
errorLoggerSpy.mockRestore();
}
expect(errorLogger).not.toHaveBeenCalled();
expect(debugLogger).toHaveBeenCalledExactlyOnceWith(
{err: error, status: 400, method: 'GET', path: '/test', requestId: undefined},
'Request rejected',
);
});
});
+134 -296
View File
@@ -20,308 +20,146 @@ import {NotFoundError} from '@fluxer/errors/src/domains/core/NotFoundError';
import {FluxerError} from '@fluxer/errors/src/FluxerError';
import {describe, expect, it} from 'vitest';
interface ErrorResponse {
code: string;
message: string;
[key: string]: unknown;
}
describe.each([
[APIErrorCodes.INVALID_REQUEST, BadRequestError, HttpStatus.BAD_REQUEST],
[APIErrorCodes.ACCESS_DENIED, ForbiddenError, HttpStatus.FORBIDDEN],
[APIErrorCodes.UNKNOWN_USER, NotFoundError, HttpStatus.NOT_FOUND],
[APIErrorCodes.GENERAL_ERROR, InternalServerError, HttpStatus.INTERNAL_SERVER_ERROR],
] as const)('%s', (code, ErrorClass, status) => {
it('uses the domain code as its default message', async () => {
const error = new ErrorClass({code});
const response = error.getResponse();
describe('Domain Errors', () => {
describe('core domain errors', () => {
describe('BadRequestError', () => {
it('should create error with required code', () => {
const error = new BadRequestError({code: APIErrorCodes.INVALID_REQUEST});
expect(error.status).toBe(400);
expect(error.code).toBe(APIErrorCodes.INVALID_REQUEST);
expect(error.message).toBe(APIErrorCodes.INVALID_REQUEST);
});
it('should allow custom message', () => {
const error = new BadRequestError({
code: APIErrorCodes.INVALID_FORM_BODY,
message: 'Custom bad request message',
});
expect(error.message).toBe('Custom bad request message');
});
it('should include data', () => {
const error = new BadRequestError({
code: APIErrorCodes.INVALID_REQUEST,
data: {field: 'test'},
});
expect(error.data).toEqual({field: 'test'});
});
it('should include headers', () => {
const error = new BadRequestError({
code: APIErrorCodes.INVALID_REQUEST,
headers: {'X-Custom': 'value'},
});
expect(error.headers).toEqual({'X-Custom': 'value'});
});
it('should include messageVariables for i18n', () => {
const error = new BadRequestError({
code: APIErrorCodes.INVALID_REQUEST,
messageVariables: {count: 5},
});
expect(error.messageVariables).toEqual({count: 5});
});
it('should be instance of FluxerError', () => {
const error = new BadRequestError({code: APIErrorCodes.INVALID_REQUEST});
expect(error).toBeInstanceOf(FluxerError);
});
expect(error).toBeInstanceOf(FluxerError);
expect(error).toMatchObject({status, code, message: code});
expect(response.status).toBe(status);
expect(response.headers.get('Content-Type')).toBe('application/json');
expect(error.toJSON()).toEqual({code, message: code});
expect(await response.json()).toEqual({code, message: code});
});
it('preserves data, headers, and localization variables', async () => {
const error = new ErrorClass({
code,
data: {field: 'email'},
headers: {'X-Permission-Required': 'admin'},
messageVariables: {count: 5},
});
describe('NotFoundError', () => {
it('should create error with status 404', () => {
const error = new NotFoundError({code: APIErrorCodes.UNKNOWN_USER});
expect(error.status).toBe(404);
expect(error.code).toBe(APIErrorCodes.UNKNOWN_USER);
expect(error.message).toBe(APIErrorCodes.UNKNOWN_USER);
});
it('should allow messageVariables for i18n', () => {
const error = new NotFoundError({
code: APIErrorCodes.UNKNOWN_USER,
messageVariables: {userId: '12345'},
});
expect(error.messageVariables).toEqual({userId: '12345'});
});
it('should be instance of FluxerError', () => {
const error = new NotFoundError({code: APIErrorCodes.UNKNOWN_USER});
expect(error).toBeInstanceOf(FluxerError);
});
});
describe('ForbiddenError', () => {
it('should create error with status 403', () => {
const error = new ForbiddenError({code: APIErrorCodes.ACCESS_DENIED});
expect(error.status).toBe(403);
expect(error.code).toBe(APIErrorCodes.ACCESS_DENIED);
expect(error.message).toBe(APIErrorCodes.ACCESS_DENIED);
});
it('should be instance of FluxerError', () => {
const error = new ForbiddenError({code: APIErrorCodes.ACCESS_DENIED});
expect(error).toBeInstanceOf(FluxerError);
});
});
describe('InternalServerError', () => {
it('should create error with status 500', () => {
const error = new InternalServerError({code: APIErrorCodes.GENERAL_ERROR});
expect(error.status).toBe(500);
expect(error.code).toBe(APIErrorCodes.GENERAL_ERROR);
expect(error.message).toBe(APIErrorCodes.GENERAL_ERROR);
});
it('should be instance of FluxerError', () => {
const error = new InternalServerError({code: APIErrorCodes.GENERAL_ERROR});
expect(error).toBeInstanceOf(FluxerError);
});
});
describe('InputValidationError', () => {
it('should create error with validation errors', () => {
const error = new InputValidationError([{path: 'email', message: 'Invalid email format'}]);
expect(error.status).toBe(400);
expect(error.code).toBe(APIErrorCodes.INVALID_FORM_BODY);
expect(error.data).toEqual({
errors: [{path: 'email', message: 'Invalid email format'}],
});
});
it('should support localized errors', () => {
const error = new InputValidationError(
[{path: 'name', message: ValidationErrorCodes.EMAIL_IS_REQUIRED}],
[{path: 'name', code: ValidationErrorCodes.EMAIL_IS_REQUIRED}],
);
expect(error.localizedErrors).toEqual([{path: 'name', code: ValidationErrorCodes.EMAIL_IS_REQUIRED}]);
expect(error.getLocalizedErrors()).toEqual([{path: 'name', code: ValidationErrorCodes.EMAIL_IS_REQUIRED}]);
});
it('should return null for localizedErrors when not provided', () => {
const error = new InputValidationError([{path: 'field', message: 'error'}]);
expect(error.localizedErrors).toBeNull();
expect(error.getLocalizedErrors()).toBeNull();
});
it('should create from single field using static method', () => {
const error = InputValidationError.create('username', 'Username is required');
expect(error).toBeInstanceOf(InputValidationError);
expect(error.data).toEqual({
errors: [{path: 'username', message: 'Username is required'}],
});
});
it('should create from multiple fields using static method', () => {
const error = InputValidationError.createMultiple([
{path: 'email', message: 'Invalid email'},
{path: 'password', message: 'Password too short'},
]);
expect(error.data).toEqual({
errors: [
{path: 'email', message: 'Invalid email'},
{path: 'password', message: 'Password too short'},
],
});
});
it('should create from error code using static method', () => {
const error = InputValidationError.fromCode('email', ValidationErrorCodes.EMAIL_IS_REQUIRED, {maxLength: 255});
expect(error.localizedErrors).toEqual([
{path: 'email', code: ValidationErrorCodes.EMAIL_IS_REQUIRED, variables: {maxLength: 255}},
]);
});
it('should create from multiple error codes using static method', () => {
const error = InputValidationError.fromCodes([
{path: 'email', code: ValidationErrorCodes.EMAIL_IS_REQUIRED},
{path: 'name', code: ValidationErrorCodes.STRING_LENGTH_INVALID, variables: {max: 100}},
]);
expect(error.localizedErrors).toHaveLength(2);
});
const response = error.getResponse();
expect(error.messageVariables).toEqual({count: 5});
expect(response.headers.get('X-Permission-Required')).toBe('admin');
expect(await response.json()).toEqual({code, message: code, field: 'email'});
});
});
it.each([
BadRequestError,
ForbiddenError,
NotFoundError,
])('preserves custom messages for client errors', (ErrorClass) => {
const error = new ErrorClass({code: APIErrorCodes.INVALID_REQUEST, message: 'Custom error message'});
expect(error.toJSON()).toEqual({code: APIErrorCodes.INVALID_REQUEST, message: 'Custom error message'});
});
describe.each([
[APIErrorCodes.INVALID_PHONE_NUMBER, InvalidPhoneNumberError, BadRequestError, HttpStatus.BAD_REQUEST],
[APIErrorCodes.PHONE_COUNTRY_NOT_SUPPORTED, PhoneCountryNotSupportedError, BadRequestError, HttpStatus.BAD_REQUEST],
[
APIErrorCodes.PHONE_INBOUND_VERIFICATION_REQUIRED,
PhoneInboundVerificationRequiredError,
BadRequestError,
HttpStatus.BAD_REQUEST,
],
[APIErrorCodes.PHONE_LOOKUP_UNAVAILABLE, PhoneLookupUnavailableError, BadRequestError, HttpStatus.BAD_REQUEST],
[APIErrorCodes.PHONE_NUMBER_NOT_IN_SERVICE, PhoneNumberNotInServiceError, BadRequestError, HttpStatus.BAD_REQUEST],
[APIErrorCodes.PHONE_NUMBER_NOT_MOBILE, PhoneNumberNotMobileError, BadRequestError, HttpStatus.BAD_REQUEST],
[
APIErrorCodes.PHONE_VERIFICATION_NEEDS_REVIEW,
PhoneVerificationNeedsReviewError,
BadRequestError,
HttpStatus.BAD_REQUEST,
],
[APIErrorCodes.UNKNOWN_CHANNEL, UnknownChannelError, NotFoundError, HttpStatus.NOT_FOUND],
[APIErrorCodes.UNKNOWN_MESSAGE, UnknownMessageError, NotFoundError, HttpStatus.NOT_FOUND],
] as const)('%s', (code, ErrorClass, BaseClass, status) => {
it('preserves the domain inheritance and response contract', async () => {
const error = new ErrorClass();
const response = error.getResponse();
expect(error).toBeInstanceOf(ErrorClass);
expect(error).toBeInstanceOf(BaseClass);
expect(error).toBeInstanceOf(FluxerError);
expect(error).toBeInstanceOf(Error);
expect(error).toMatchObject({code, status});
expect(error.toJSON()).toEqual({code, message: code});
expect(response.status).toBe(status);
expect(await response.json()).toEqual({code, message: code});
});
});
describe('InputValidationError', () => {
const errors = [
{path: 'email', message: 'Invalid email'},
{path: 'password', message: 'Password too short'},
];
it.each([
['constructor', () => new InputValidationError(errors), errors],
['createMultiple', () => InputValidationError.createMultiple(errors), errors],
['create', () => InputValidationError.create('email', 'Invalid email'), [errors[0]]],
] as const)('%s retains every field error in the response', async (_name, createError, expectedErrors) => {
const error = createError();
const response = error.getResponse();
expect(error).toBeInstanceOf(InputValidationError);
expect(error.data).toEqual({errors: expectedErrors});
expect(error.localizedErrors).toBeNull();
expect(error.getLocalizedErrors()).toBeNull();
expect(response.status).toBe(HttpStatus.BAD_REQUEST);
expect(await response.json()).toEqual({
code: APIErrorCodes.INVALID_FORM_BODY,
message: APIErrorCodes.INVALID_FORM_BODY,
errors: expectedErrors,
});
});
describe('auth domain errors', () => {
describe('InvalidPhoneNumberError', () => {
it('should have correct code from APIErrorCodes', () => {
const error = new InvalidPhoneNumberError();
expect(error.code).toBe(APIErrorCodes.INVALID_PHONE_NUMBER);
expect(error.status).toBe(HttpStatus.BAD_REQUEST);
});
it('should be instance of BadRequestError', () => {
const error = new InvalidPhoneNumberError();
expect(error).toBeInstanceOf(BadRequestError);
});
it('should be instance of FluxerError', () => {
const error = new InvalidPhoneNumberError();
expect(error).toBeInstanceOf(FluxerError);
});
});
describe.each([
[PhoneCountryNotSupportedError, APIErrorCodes.PHONE_COUNTRY_NOT_SUPPORTED],
[PhoneInboundVerificationRequiredError, APIErrorCodes.PHONE_INBOUND_VERIFICATION_REQUIRED],
[PhoneLookupUnavailableError, APIErrorCodes.PHONE_LOOKUP_UNAVAILABLE],
[PhoneNumberNotInServiceError, APIErrorCodes.PHONE_NUMBER_NOT_IN_SERVICE],
[PhoneNumberNotMobileError, APIErrorCodes.PHONE_NUMBER_NOT_MOBILE],
[PhoneVerificationNeedsReviewError, APIErrorCodes.PHONE_VERIFICATION_NEEDS_REVIEW],
] as const)('%p', (ErrorClass, code) => {
it('should have correct code from APIErrorCodes', () => {
const error = new ErrorClass();
expect(error.code).toBe(code);
expect(error.status).toBe(HttpStatus.BAD_REQUEST);
});
it('should be instance of BadRequestError', () => {
const error = new ErrorClass();
expect(error).toBeInstanceOf(BadRequestError);
});
it('should generate a response body carrying the code', async () => {
const error = new ErrorClass();
const response = error.getResponse();
expect(response.status).toBe(400);
const body = (await response.json()) as ErrorResponse;
expect(body).toEqual({code, message: code});
});
});
it('retains explicitly supplied localization metadata separately from field messages', () => {
const localizedErrors = [{path: 'email', code: ValidationErrorCodes.EMAIL_IS_REQUIRED}];
const error = new InputValidationError(errors, localizedErrors);
expect(error.data).toEqual({errors});
expect(error.localizedErrors).toEqual(localizedErrors);
expect(error.getLocalizedErrors()).toEqual(localizedErrors);
});
describe('channel domain errors', () => {
describe('UnknownChannelError', () => {
it('should have correct code from APIErrorCodes', () => {
const error = new UnknownChannelError();
expect(error.code).toBe(APIErrorCodes.UNKNOWN_CHANNEL);
expect(error.status).toBe(HttpStatus.NOT_FOUND);
});
it('should be instance of NotFoundError', () => {
const error = new UnknownChannelError();
expect(error).toBeInstanceOf(NotFoundError);
});
it('should be instance of FluxerError', () => {
const error = new UnknownChannelError();
expect(error).toBeInstanceOf(FluxerError);
});
});
describe('UnknownMessageError', () => {
it('should have correct code from APIErrorCodes', () => {
const error = new UnknownMessageError();
expect(error.code).toBe(APIErrorCodes.UNKNOWN_MESSAGE);
expect(error.status).toBe(HttpStatus.NOT_FOUND);
});
it('should be instance of NotFoundError', () => {
const error = new UnknownMessageError();
expect(error).toBeInstanceOf(NotFoundError);
});
});
it('creates a localized field error with interpolation variables', () => {
const code = ValidationErrorCodes.STRING_LENGTH_INVALID;
const variables = {min: 1, max: 100};
const error = InputValidationError.fromCode('name', code, variables);
expect(error.data).toEqual({errors: [{path: 'name', code, message: code}]});
expect(error.getLocalizedErrors()).toEqual([{path: 'name', code, variables}]);
});
describe('error response generation', () => {
it('should generate correct JSON response for domain errors', async () => {
const error = new UnknownChannelError();
const response = error.getResponse();
expect(response.status).toBe(404);
expect(response.headers.get('Content-Type')).toBe('application/json');
const body = (await response.json()) as ErrorResponse;
expect(body.code).toBe(APIErrorCodes.UNKNOWN_CHANNEL);
});
it('should include data in response', async () => {
const error = new BadRequestError({
code: APIErrorCodes.INVALID_REQUEST,
data: {field: 'test', reason: 'invalid'},
});
const response = error.getResponse();
const body = (await response.json()) as ErrorResponse;
expect(body).toEqual({
code: APIErrorCodes.INVALID_REQUEST,
message: APIErrorCodes.INVALID_REQUEST,
field: 'test',
reason: 'invalid',
});
});
it('should include custom headers in response', async () => {
const error = new ForbiddenError({
code: APIErrorCodes.ACCESS_DENIED,
headers: {'X-Permission-Required': 'admin'},
});
const response = error.getResponse();
expect(response.headers.get('X-Permission-Required')).toBe('admin');
});
});
describe('error serialization', () => {
it('should serialize domain errors to JSON correctly', () => {
const error = new UnknownChannelError();
const json = error.toJSON();
expect(json).toEqual({
code: APIErrorCodes.UNKNOWN_CHANNEL,
message: APIErrorCodes.UNKNOWN_CHANNEL,
});
});
it('should include data in JSON serialization', () => {
const error = new BadRequestError({
code: APIErrorCodes.INVALID_REQUEST,
data: {extra: 'info'},
});
const json = error.toJSON();
expect(json).toEqual({
code: APIErrorCodes.INVALID_REQUEST,
message: APIErrorCodes.INVALID_REQUEST,
extra: 'info',
});
});
});
describe('error inheritance chain', () => {
it('should maintain correct prototype chain', () => {
const error = new InvalidPhoneNumberError();
expect(error).toBeInstanceOf(InvalidPhoneNumberError);
expect(error).toBeInstanceOf(BadRequestError);
expect(error).toBeInstanceOf(FluxerError);
expect(error).toBeInstanceOf(Error);
});
it('should be catchable at any level of the chain', () => {
const error = new UnknownChannelError();
try {
throw error;
} catch (e) {
if (e instanceof NotFoundError) {
expect(e.code).toBe(APIErrorCodes.UNKNOWN_CHANNEL);
}
}
try {
throw error;
} catch (e) {
if (e instanceof FluxerError) {
expect(e.status).toBe(404);
}
}
try {
throw error;
} catch (e) {
if (e instanceof Error) {
expect(e).toBeInstanceOf(UnknownChannelError);
}
}
it('preserves all localized codes, paths, and variables in order', () => {
const localizedErrors = [
{path: 'email', code: ValidationErrorCodes.EMAIL_IS_REQUIRED},
{path: 'name', code: ValidationErrorCodes.STRING_LENGTH_INVALID, variables: {max: 100}},
];
const error = InputValidationError.fromCodes(localizedErrors);
expect(error.getLocalizedErrors()).toEqual(localizedErrors);
expect(error.data).toEqual({
errors: [
{path: 'email', code: ValidationErrorCodes.EMAIL_IS_REQUIRED, message: ValidationErrorCodes.EMAIL_IS_REQUIRED},
{
path: 'name',
code: ValidationErrorCodes.STRING_LENGTH_INVALID,
message: ValidationErrorCodes.STRING_LENGTH_INVALID,
},
],
});
});
});
+72 -199
View File
@@ -5,208 +5,81 @@ import {HTTPException} from 'hono/http-exception';
import {describe, expect, it} from 'vitest';
describe('FluxerError', () => {
describe('constructor', () => {
it('should create an error with required options', () => {
const error = new FluxerError({
code: 'TEST_ERROR',
status: 400,
});
expect(error.code).toBe('TEST_ERROR');
expect(error.status).toBe(400);
expect(error.message).toBe('TEST_ERROR');
expect(error.name).toBe('FluxerError');
});
it('should use provided message instead of code', () => {
const error = new FluxerError({
code: 'TEST_ERROR',
message: 'Custom error message',
status: 400,
});
expect(error.code).toBe('TEST_ERROR');
expect(error.message).toBe('Custom error message');
});
it('should include optional data', () => {
const error = new FluxerError({
code: 'TEST_ERROR',
status: 400,
data: {field: 'username', reason: 'invalid'},
});
expect(error.data).toEqual({field: 'username', reason: 'invalid'});
});
it('should include optional headers', () => {
const error = new FluxerError({
code: 'TEST_ERROR',
status: 400,
headers: {'X-Custom-Header': 'value'},
});
expect(error.headers).toEqual({'X-Custom-Header': 'value'});
});
it('should include message variables for i18n', () => {
const error = new FluxerError({
code: 'RATE_LIMITED',
status: 429,
messageVariables: {retryAfter: 60},
});
expect(error.messageVariables).toEqual({retryAfter: 60});
});
it('should include cause for error chaining', () => {
const cause = new Error('Original error');
const error = new FluxerError({
code: 'WRAPPED_ERROR',
status: 500,
cause,
});
expect(error.cause).toBe(cause);
});
it('should be an instance of HTTPException', () => {
const error = new FluxerError({
code: 'TEST_ERROR',
status: 400,
});
expect(error).toBeInstanceOf(HTTPException);
});
});
describe('getResponse', () => {
it('should return a JSON Response with correct status', async () => {
const error = new FluxerError({
code: 'TEST_ERROR',
message: 'Test message',
status: 400,
});
const response = error.getResponse();
expect(response.status).toBe(400);
expect(response.headers.get('Content-Type')).toBe('application/json');
const body = await response.json();
expect(body).toEqual({
code: 'TEST_ERROR',
message: 'Test message',
});
});
it('should include data in response body', async () => {
const error = new FluxerError({
it.each([
{
name: 'defaults the message to the code',
create: () => new FluxerError({code: 'BAD_REQUEST', status: 400}),
status: 400,
body: {code: 'BAD_REQUEST', message: 'BAD_REQUEST'},
},
{
name: 'uses a custom message',
create: () => new FluxerError({code: 'SERVER_ERROR', message: 'Custom error message', status: 500}),
status: 500,
body: {code: 'SERVER_ERROR', message: 'Custom error message'},
},
{
name: 'preserves an intentionally empty message',
create: () => new FluxerError({code: 'FORBIDDEN', message: '', status: 403}),
status: 403,
body: {code: 'FORBIDDEN', message: ''},
},
{
name: 'includes field error data',
create: () =>
new FluxerError({
code: 'VALIDATION_ERROR',
message: 'Validation failed',
status: 400,
data: {errors: [{path: 'email', code: 'INVALID', message: 'Invalid email'}]},
}),
status: 400,
body: {
code: 'VALIDATION_ERROR',
message: 'Validation failed',
status: 400,
data: {errors: [{field: 'email', message: 'Invalid email'}]},
});
const response = error.getResponse();
const body = await response.json();
expect(body).toEqual({
code: 'VALIDATION_ERROR',
message: 'Validation failed',
errors: [{field: 'email', message: 'Invalid email'}],
});
});
it('should include custom headers in response', async () => {
const error = new FluxerError({
code: 'RATE_LIMITED',
status: 429,
headers: {'Retry-After': '60', 'X-RateLimit-Reset': '1234567890'},
});
const response = error.getResponse();
expect(response.headers.get('Retry-After')).toBe('60');
expect(response.headers.get('X-RateLimit-Reset')).toBe('1234567890');
expect(response.headers.get('Content-Type')).toBe('application/json');
});
it('should handle empty data', async () => {
const error = new FluxerError({
code: 'SIMPLE_ERROR',
status: 403,
});
const response = error.getResponse();
const body = await response.json();
expect(body).toEqual({
code: 'SIMPLE_ERROR',
message: 'SIMPLE_ERROR',
});
});
errors: [{path: 'email', code: 'INVALID', message: 'Invalid email'}],
},
},
])('$name in JSON serialization and the HTTP response', async ({create, status, body}) => {
const error = create();
const response = error.getResponse();
expect(error.status).toBe(status);
expect(error.toJSON()).toEqual(body);
expect(response.status).toBe(status);
expect(response.headers.get('Content-Type')).toBe('application/json');
expect(await response.json()).toEqual(body);
});
describe('toJSON', () => {
it('should serialize to JSON object', () => {
const error = new FluxerError({
code: 'TEST_ERROR',
message: 'Test message',
status: 400,
});
const json = error.toJSON();
expect(json).toEqual({
code: 'TEST_ERROR',
message: 'Test message',
});
});
it('should include data in JSON output', () => {
const error = new FluxerError({
code: 'VALIDATION_ERROR',
message: 'Validation failed',
status: 400,
data: {field: 'username'},
});
const json = error.toJSON();
expect(json).toEqual({
code: 'VALIDATION_ERROR',
message: 'Validation failed',
field: 'username',
});
});
it('should not include status or headers in JSON output', () => {
const error = new FluxerError({
code: 'TEST_ERROR',
status: 500,
headers: {'X-Custom': 'value'},
});
const json = error.toJSON();
expect(json).not.toHaveProperty('status');
expect(json).not.toHaveProperty('headers');
});
});
describe('status codes', () => {
it('should handle 4xx client error status codes', () => {
const testCases = [
{status: 400, name: 'Bad Request'},
{status: 401, name: 'Unauthorized'},
{status: 403, name: 'Forbidden'},
{status: 404, name: 'Not Found'},
{status: 409, name: 'Conflict'},
{status: 429, name: 'Too Many Requests'},
] as const;
for (const {status} of testCases) {
const error = new FluxerError({code: 'TEST', status});
expect(error.status).toBe(status);
}
});
it('should handle 5xx server error status codes', () => {
const testCases = [
{status: 500, name: 'Internal Server Error'},
{status: 501, name: 'Not Implemented'},
{status: 502, name: 'Bad Gateway'},
{status: 503, name: 'Service Unavailable'},
{status: 504, name: 'Gateway Timeout'},
] as const;
for (const {status} of testCases) {
const error = new FluxerError({code: 'TEST', status});
expect(error.status).toBe(status);
}
});
});
describe('error properties', () => {
it('should have correct name property', () => {
const error = new FluxerError({code: 'TEST', status: 400});
expect(error.name).toBe('FluxerError');
});
it('should be throwable', () => {
const error = new FluxerError({code: 'THROWN_ERROR', status: 400});
expect(() => {
throw error;
}).toThrow(FluxerError);
});
it('should be catchable as Error', () => {
const error = new FluxerError({code: 'CAUGHT_ERROR', status: 400});
try {
throw error;
} catch (e) {
expect(e).toBeInstanceOf(Error);
expect(e).toBeInstanceOf(FluxerError);
}
it('preserves exception metadata and headers without leaking them into the body', async () => {
const cause = new Error('Original error');
const headers = {'Retry-After': '60', 'X-RateLimit-Reset': '1234567890'};
const data = {retry_after: 60};
const messageVariables = {retryAfter: 60};
const error = new FluxerError({
code: 'RATE_LIMITED',
status: 429,
cause,
headers,
data,
messageVariables,
});
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(HTTPException);
expect(error.name).toBe('FluxerError');
expect(error.code).toBe('RATE_LIMITED');
expect(error.message).toBe('RATE_LIMITED');
expect(error.cause).toBe(cause);
expect(error.data).toEqual(data);
expect(error.headers).toEqual(headers);
expect(error.messageVariables).toEqual(messageVariables);
const response = error.getResponse();
expect(response.status).toBe(429);
expect(response.headers.get('Content-Type')).toBe('application/json');
expect(response.headers.get('Retry-After')).toBe('60');
expect(response.headers.get('X-RateLimit-Reset')).toBe('1234567890');
const body = {code: 'RATE_LIMITED', message: 'RATE_LIMITED', retry_after: 60};
expect(error.toJSON()).toEqual(body);
expect(await response.json()).toEqual(body);
});
});
+54 -237
View File
@@ -18,247 +18,64 @@ import {
} from '@fluxer/errors/src/HttpErrors';
import {describe, expect, it} from 'vitest';
describe('HttpErrors', () => {
describe('BadRequestError', () => {
it('should have status 400 and default code/message', () => {
const error = new BadRequestError();
expect(error.status).toBe(HttpStatus.BAD_REQUEST);
expect(error.code).toBe('BAD_REQUEST');
expect(error.message).toBe('Bad Request');
expect(error.name).toBe('BadRequestError');
describe.each([
['BAD_REQUEST', BadRequestError, HttpStatus.BAD_REQUEST, 'Bad Request'],
['UNAUTHORIZED', UnauthorizedError, HttpStatus.UNAUTHORIZED, 'Unauthorized'],
['FORBIDDEN', ForbiddenError, HttpStatus.FORBIDDEN, 'Forbidden'],
['NOT_FOUND', NotFoundError, HttpStatus.NOT_FOUND, 'Not Found'],
['METHOD_NOT_ALLOWED', MethodNotAllowedError, HttpStatus.METHOD_NOT_ALLOWED, 'Method Not Allowed'],
['CONFLICT', ConflictError, HttpStatus.CONFLICT, 'Conflict'],
['GONE', GoneError, HttpStatus.GONE, 'Gone'],
['INTERNAL_SERVER_ERROR', InternalServerError, HttpStatus.INTERNAL_SERVER_ERROR, 'Internal Server Error'],
['NOT_IMPLEMENTED', NotImplementedError, HttpStatus.NOT_IMPLEMENTED, 'Not Implemented'],
['BAD_GATEWAY', BadGatewayError, HttpStatus.BAD_GATEWAY, 'Bad Gateway'],
['SERVICE_UNAVAILABLE', ServiceUnavailableError, HttpStatus.SERVICE_UNAVAILABLE, 'Service Unavailable'],
['GATEWAY_TIMEOUT', GatewayTimeoutError, HttpStatus.GATEWAY_TIMEOUT, 'Gateway Timeout'],
] as const)('%s', (code, ErrorClass, status, message) => {
it('serializes the default error with its HTTP status', async () => {
const error = new ErrorClass();
const response = error.getResponse();
expect(error).toBeInstanceOf(FluxerError);
expect(error).toBeInstanceOf(Error);
expect(error).toMatchObject({name: ErrorClass.name, status, code, message});
expect(error.toJSON()).toEqual({code, message});
expect(response.status).toBe(status);
expect(response.headers.get('Content-Type')).toBe('application/json');
expect(await response.json()).toEqual({code, message});
});
it('forwards overrides without leaking the cause into the response', async () => {
const cause = new Error('Upstream operation failed');
const error = new ErrorClass({
code: 'CUSTOM_ERROR',
message: 'Request could not be completed',
data: {field: 'email'},
headers: {'X-Error-Type': 'validation'},
cause,
});
it('should allow custom code', () => {
const error = new BadRequestError({code: 'INVALID_INPUT'});
expect(error.code).toBe('INVALID_INPUT');
expect(error.message).toBe('Bad Request');
});
it('should allow custom message', () => {
const error = new BadRequestError({message: 'Invalid request payload'});
expect(error.code).toBe('BAD_REQUEST');
expect(error.message).toBe('Invalid request payload');
});
it('should allow custom code and message', () => {
const error = new BadRequestError({
code: 'VALIDATION_FAILED',
message: 'Request validation failed',
});
expect(error.code).toBe('VALIDATION_FAILED');
expect(error.message).toBe('Request validation failed');
});
it('should include data', () => {
const error = new BadRequestError({
data: {field: 'email', reason: 'invalid format'},
});
expect(error.data).toEqual({field: 'email', reason: 'invalid format'});
});
it('should include headers', () => {
const error = new BadRequestError({
headers: {'X-Error-Type': 'validation'},
});
expect(error.headers).toEqual({'X-Error-Type': 'validation'});
});
it('should include cause', () => {
const cause = new Error('Original error');
const error = new BadRequestError({cause});
expect(error.cause).toBe(cause);
});
it('should be instance of FluxerError', () => {
const error = new BadRequestError();
expect(error).toBeInstanceOf(FluxerError);
const response = error.getResponse();
expect(error.status).toBe(status);
expect(error.cause).toBe(cause);
expect(error.data).toEqual({field: 'email'});
expect(response.headers.get('X-Error-Type')).toBe('validation');
expect(await response.json()).toEqual({
code: 'CUSTOM_ERROR',
message: 'Request could not be completed',
field: 'email',
});
});
describe('UnauthorizedError', () => {
it('should have status 401 and default code/message', () => {
const error = new UnauthorizedError();
expect(error.status).toBe(HttpStatus.UNAUTHORIZED);
expect(error.code).toBe('UNAUTHORIZED');
expect(error.message).toBe('Unauthorized');
expect(error.name).toBe('UnauthorizedError');
});
it('should allow custom code', () => {
const error = new UnauthorizedError({code: 'INVALID_TOKEN'});
expect(error.code).toBe('INVALID_TOKEN');
});
it('should include WWW-Authenticate header', () => {
const error = new UnauthorizedError({
headers: {'WWW-Authenticate': 'Bearer realm="api"'},
});
expect(error.headers).toEqual({'WWW-Authenticate': 'Bearer realm="api"'});
});
it('keeps the default message when only the code changes', () => {
expect(new ErrorClass({code: 'CUSTOM_ERROR'}).toJSON()).toEqual({code: 'CUSTOM_ERROR', message});
});
describe('ForbiddenError', () => {
it('should have status 403 and default code/message', () => {
const error = new ForbiddenError();
expect(error.status).toBe(HttpStatus.FORBIDDEN);
expect(error.code).toBe('FORBIDDEN');
expect(error.message).toBe('Forbidden');
expect(error.name).toBe('ForbiddenError');
});
it('should allow custom code for permission errors', () => {
const error = new ForbiddenError({code: 'MISSING_PERMISSIONS'});
expect(error.code).toBe('MISSING_PERMISSIONS');
});
it('keeps the default code when only the message changes', () => {
expect(new ErrorClass({message: 'Custom message'}).toJSON()).toEqual({code, message: 'Custom message'});
});
describe('NotFoundError', () => {
it('should have status 404 and default code/message', () => {
const error = new NotFoundError();
expect(error.status).toBe(HttpStatus.NOT_FOUND);
expect(error.code).toBe('NOT_FOUND');
expect(error.message).toBe('Not Found');
expect(error.name).toBe('NotFoundError');
});
it('should allow custom code for resource not found', () => {
const error = new NotFoundError({code: 'UNKNOWN_USER'});
expect(error.code).toBe('UNKNOWN_USER');
});
});
describe('MethodNotAllowedError', () => {
it('should have status 405 and default code/message', () => {
const error = new MethodNotAllowedError();
expect(error.status).toBe(HttpStatus.METHOD_NOT_ALLOWED);
expect(error.code).toBe('METHOD_NOT_ALLOWED');
expect(error.message).toBe('Method Not Allowed');
expect(error.name).toBe('MethodNotAllowedError');
});
it('should include Allow header', () => {
const error = new MethodNotAllowedError({
headers: {Allow: 'GET, POST'},
});
expect(error.headers).toEqual({Allow: 'GET, POST'});
});
});
describe('ConflictError', () => {
it('should have status 409 and default code/message', () => {
const error = new ConflictError();
expect(error.status).toBe(HttpStatus.CONFLICT);
expect(error.code).toBe('CONFLICT');
expect(error.message).toBe('Conflict');
expect(error.name).toBe('ConflictError');
});
it('should allow custom code for conflict scenarios', () => {
const error = new ConflictError({code: 'USERNAME_TAKEN'});
expect(error.code).toBe('USERNAME_TAKEN');
});
});
describe('GoneError', () => {
it('should have status 410 and default code/message', () => {
const error = new GoneError();
expect(error.status).toBe(HttpStatus.GONE);
expect(error.code).toBe('GONE');
expect(error.message).toBe('Gone');
expect(error.name).toBe('GoneError');
});
it('should allow custom code for deleted resources', () => {
const error = new GoneError({code: 'RESOURCE_DELETED'});
expect(error.code).toBe('RESOURCE_DELETED');
});
});
describe('InternalServerError', () => {
it('should have status 500 and default code/message', () => {
const error = new InternalServerError();
expect(error.status).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
expect(error.code).toBe('INTERNAL_SERVER_ERROR');
expect(error.message).toBe('Internal Server Error');
expect(error.name).toBe('InternalServerError');
});
it('should allow custom code', () => {
const error = new InternalServerError({code: 'DATABASE_ERROR'});
expect(error.code).toBe('DATABASE_ERROR');
});
it('should preserve cause for debugging', () => {
const cause = new Error('Database connection failed');
const error = new InternalServerError({cause});
expect(error.cause).toBe(cause);
});
});
describe('NotImplementedError', () => {
it('should have status 501 and default code/message', () => {
const error = new NotImplementedError();
expect(error.status).toBe(HttpStatus.NOT_IMPLEMENTED);
expect(error.code).toBe('NOT_IMPLEMENTED');
expect(error.message).toBe('Not Implemented');
expect(error.name).toBe('NotImplementedError');
});
});
describe('ServiceUnavailableError', () => {
it('should have status 503 and default code/message', () => {
const error = new ServiceUnavailableError();
expect(error.status).toBe(HttpStatus.SERVICE_UNAVAILABLE);
expect(error.code).toBe('SERVICE_UNAVAILABLE');
expect(error.message).toBe('Service Unavailable');
expect(error.name).toBe('ServiceUnavailableError');
});
it('should include Retry-After header', () => {
const error = new ServiceUnavailableError({
headers: {'Retry-After': '300'},
});
expect(error.headers).toEqual({'Retry-After': '300'});
});
});
describe('BadGatewayError', () => {
it('should have status 502 and default code/message', () => {
const error = new BadGatewayError();
expect(error.status).toBe(HttpStatus.BAD_GATEWAY);
expect(error.code).toBe('BAD_GATEWAY');
expect(error.message).toBe('Bad Gateway');
expect(error.name).toBe('BadGatewayError');
});
});
describe('GatewayTimeoutError', () => {
it('should have status 504 and default code/message', () => {
const error = new GatewayTimeoutError();
expect(error.status).toBe(HttpStatus.GATEWAY_TIMEOUT);
expect(error.code).toBe('GATEWAY_TIMEOUT');
expect(error.message).toBe('Gateway Timeout');
expect(error.name).toBe('GatewayTimeoutError');
});
});
describe('error inheritance', () => {
it('all HTTP errors should extend FluxerError', () => {
const errors = [
new BadRequestError(),
new UnauthorizedError(),
new ForbiddenError(),
new NotFoundError(),
new MethodNotAllowedError(),
new ConflictError(),
new GoneError(),
new InternalServerError(),
new NotImplementedError(),
new ServiceUnavailableError(),
new BadGatewayError(),
new GatewayTimeoutError(),
];
for (const error of errors) {
expect(error).toBeInstanceOf(FluxerError);
expect(error).toBeInstanceOf(Error);
}
});
});
describe('response generation', () => {
it('should generate correct response for each error type', async () => {
const testCases = [
{error: new BadRequestError(), expectedStatus: 400},
{error: new UnauthorizedError(), expectedStatus: 401},
{error: new ForbiddenError(), expectedStatus: 403},
{error: new NotFoundError(), expectedStatus: 404},
{error: new MethodNotAllowedError(), expectedStatus: 405},
{error: new ConflictError(), expectedStatus: 409},
{error: new GoneError(), expectedStatus: 410},
{error: new InternalServerError(), expectedStatus: 500},
{error: new NotImplementedError(), expectedStatus: 501},
{error: new BadGatewayError(), expectedStatus: 502},
{error: new ServiceUnavailableError(), expectedStatus: 503},
{error: new GatewayTimeoutError(), expectedStatus: 504},
];
for (const {error, expectedStatus} of testCases) {
const response = error.getResponse();
expect(response.status).toBe(expectedStatus);
expect(response.headers.get('Content-Type')).toBe('application/json');
const body = await response.json();
expect(body).toHaveProperty('code');
expect(body).toHaveProperty('message');
}
});
it('does not replace explicitly empty overrides with defaults', () => {
expect(new ErrorClass({code: '', message: ''}).toJSON()).toEqual({code: '', message: ''});
});
});
@@ -5,170 +5,76 @@ import {FluxerError} from '@fluxer/errors/src/FluxerError';
import {ValidationError} from '@fluxer/errors/src/ValidationError';
import {describe, expect, it} from 'vitest';
interface ValidationErrorBody {
code: string;
message: string;
errors: Array<{
path: string;
code: string;
message: string;
}>;
}
describe('ValidationError', () => {
describe('constructor', () => {
it('should create error with single path error', () => {
const error = new ValidationError({
errors: [{path: 'email', code: 'INVALID_EMAIL', message: 'Invalid email format'}],
});
expect(error.status).toBe(HttpStatus.BAD_REQUEST);
expect(error.code).toBe('VALIDATION_ERROR');
expect(error.message).toBe('Validation failed');
expect(error.name).toBe('ValidationError');
expect(error.errors).toEqual([{path: 'email', code: 'INVALID_EMAIL', message: 'Invalid email format'}]);
});
it('should create error with multiple path errors', () => {
const pathErrors = [
{path: 'email', code: 'REQUIRED', message: 'Email is required'},
{path: 'password', code: 'TOO_SHORT', message: 'Password must be at least 8 characters'},
{path: 'username', code: 'INVALID_CHARS', message: 'Username contains invalid characters'},
];
const error = new ValidationError({errors: pathErrors});
expect(error.errors).toHaveLength(3);
expect(error.errors).toEqual(pathErrors);
});
it('should allow custom code', () => {
const error = new ValidationError({
code: 'INVALID_FORM_BODY',
errors: [{path: 'name', code: 'REQUIRED', message: 'Name is required'}],
});
expect(error.code).toBe('INVALID_FORM_BODY');
});
it('should allow custom message', () => {
const error = new ValidationError({
message: 'Input validation failed',
errors: [{path: 'name', code: 'REQUIRED', message: 'Name is required'}],
});
expect(error.message).toBe('Input validation failed');
});
it('should be instance of FluxerError', () => {
const error = new ValidationError({
errors: [{path: 'field', code: 'CODE', message: 'message'}],
});
expect(error).toBeInstanceOf(FluxerError);
it.each([
{name: 'single field', errors: [{path: 'email', code: 'INVALID_EMAIL', message: 'Invalid email format'}]},
{
name: 'nested and array fields',
errors: [
{path: 'user.email', code: 'REQUIRED', message: "Email can't be empty"},
{path: 'address.street', code: 'REQUIRED', message: 'Street is required'},
{path: 'items[0].name', code: 'REQUIRED', message: 'Item name is required'},
{path: 'items[1].quantity', code: 'MIN', message: 'Quantity must be at least 1'},
],
},
{name: 'empty field list', errors: []},
])('preserves $name errors through construction and serialization', async ({errors}) => {
const error = new ValidationError({errors});
const body = {code: 'VALIDATION_ERROR', message: 'Validation failed', errors};
expect(error).toBeInstanceOf(FluxerError);
expect(error.name).toBe('ValidationError');
expect(error.status).toBe(HttpStatus.BAD_REQUEST);
expect(error.code).toBe(body.code);
expect(error.message).toBe(body.message);
expect(error.errors).toEqual(errors);
expect(error.data).toEqual({errors});
expect(error.toJSON()).toEqual(body);
const response = error.getResponse();
expect(response.status).toBe(HttpStatus.BAD_REQUEST);
expect(response.headers.get('Content-Type')).toBe('application/json');
expect(await response.json()).toEqual(body);
});
it.each([
{options: {code: 'CUSTOM_VALIDATION'}, code: 'CUSTOM_VALIDATION', message: 'Validation failed'},
{options: {message: 'Input validation failed'}, code: 'VALIDATION_ERROR', message: 'Input validation failed'},
{
options: {code: 'CUSTOM_VALIDATION', message: 'Input validation failed'},
code: 'CUSTOM_VALIDATION',
message: 'Input validation failed',
},
])('applies code/message overrides $options without changing field errors', async ({options, code, message}) => {
const errors = [{path: 'name', code: 'REQUIRED', message: 'Name is required'}];
const error = new ValidationError({...options, errors});
const body = {code, message, errors};
expect(error.code).toBe(code);
expect(error.message).toBe(message);
expect(error.toJSON()).toEqual(body);
expect(await error.getResponse().json()).toEqual(body);
});
it('fromPath constructs one field error with the default response contract', () => {
const error = ValidationError.fromPath('username', 'TAKEN', 'Username is already taken');
expect(error).toBeInstanceOf(ValidationError);
expect(error.toJSON()).toEqual({
code: 'VALIDATION_ERROR',
message: 'Validation failed',
errors: [{path: 'username', code: 'TAKEN', message: 'Username is already taken'}],
});
});
describe('getResponse', () => {
it('should return JSON response with errors array', async () => {
const error = new ValidationError({
errors: [{path: 'email', code: 'INVALID', message: 'Invalid email'}],
});
const response = error.getResponse();
expect(response.status).toBe(400);
expect(response.headers.get('Content-Type')).toBe('application/json');
const body = await response.json();
expect(body).toEqual({
code: 'VALIDATION_ERROR',
message: 'Validation failed',
errors: [{path: 'email', code: 'INVALID', message: 'Invalid email'}],
});
});
it('should include multiple errors in response', async () => {
const pathErrors = [
{path: 'email', code: 'REQUIRED', message: 'Email is required'},
{path: 'password', code: 'TOO_SHORT', message: 'Password too short'},
];
const error = new ValidationError({errors: pathErrors});
const response = error.getResponse();
const body = (await response.json()) as ValidationErrorBody;
expect(body.errors).toHaveLength(2);
expect(body.errors).toEqual(pathErrors);
});
it('should include custom code and message in response', async () => {
const error = new ValidationError({
code: 'CUSTOM_VALIDATION',
message: 'Custom validation message',
errors: [{path: 'field', code: 'CODE', message: 'message'}],
});
const response = error.getResponse();
const body = (await response.json()) as ValidationErrorBody;
expect(body.code).toBe('CUSTOM_VALIDATION');
expect(body.message).toBe('Custom validation message');
});
});
describe('fromPath static method', () => {
it('should create ValidationError from single path', () => {
const error = ValidationError.fromPath('username', 'TAKEN', 'Username is already taken');
expect(error).toBeInstanceOf(ValidationError);
expect(error.errors).toEqual([{path: 'username', code: 'TAKEN', message: 'Username is already taken'}]);
});
it('should have default code and message', () => {
const error = ValidationError.fromPath('field', 'code', 'message');
expect(error.code).toBe('VALIDATION_ERROR');
expect(error.message).toBe('Validation failed');
});
});
describe('fromPaths static method', () => {
it('should create ValidationError from multiple paths', () => {
const pathErrors = [
it.each([
{errors: []},
{
errors: [
{path: 'email', code: 'REQUIRED', message: 'Email is required'},
{path: 'password', code: 'WEAK', message: 'Password is too weak'},
];
const error = ValidationError.fromPaths(pathErrors);
expect(error).toBeInstanceOf(ValidationError);
expect(error.errors).toEqual(pathErrors);
});
it('should handle empty array', () => {
const error = ValidationError.fromPaths([]);
expect(error.errors).toEqual([]);
});
});
describe('error data structure', () => {
it('should store errors in data property', () => {
const pathErrors = [{path: 'test', code: 'TEST', message: 'Test error'}];
const error = new ValidationError({errors: pathErrors});
expect(error.data).toEqual({errors: pathErrors});
});
it('should serialize correctly with toJSON', () => {
const error = new ValidationError({
errors: [{path: 'field', code: 'CODE', message: 'message'}],
});
const json = error.toJSON();
expect(json).toEqual({
code: 'VALIDATION_ERROR',
message: 'Validation failed',
errors: [{path: 'field', code: 'CODE', message: 'message'}],
});
});
});
describe('edge cases', () => {
it('should handle paths with special characters', () => {
const error = new ValidationError({
errors: [{path: 'user.email', code: 'INVALID', message: "Email can't be empty"}],
});
expect(error.errors[0].path).toBe('user.email');
expect(error.errors[0].message).toBe("Email can't be empty");
});
it('should handle nested field paths', () => {
const error = new ValidationError({
errors: [
{path: 'address.street', code: 'REQUIRED', message: 'Street is required'},
{path: 'address.city', code: 'REQUIRED', message: 'City is required'},
{path: 'address.zip', code: 'INVALID_FORMAT', message: 'Invalid ZIP code format'},
],
});
expect(error.errors).toHaveLength(3);
expect(error.errors[0].path).toBe('address.street');
});
it('should handle array index field paths', () => {
const error = new ValidationError({
errors: [
{path: 'items[0].name', code: 'REQUIRED', message: 'Item name is required'},
{path: 'items[1].quantity', code: 'MIN', message: 'Quantity must be at least 1'},
],
});
expect(error.errors).toHaveLength(2);
expect(error.errors[0].path).toBe('items[0].name');
});
],
},
])('fromPaths preserves the supplied fields $errors', ({errors}) => {
const error = ValidationError.fromPaths(errors);
expect(error).toBeInstanceOf(ValidationError);
expect(error.errors).toEqual(errors);
expect(error.toJSON()).toEqual({code: 'VALIDATION_ERROR', message: 'Validation failed', errors});
});
});
@@ -6,7 +6,7 @@ import {
sanitizeRetryAfterSeconds,
} from '@fluxer/errors/src/domains/core/RetryAfterSeconds';
import {ThrottledError} from '@fluxer/errors/src/domains/core/ThrottledError';
import type {FluxerErrorData} from '@fluxer/errors/src/FluxerError';
import type {RateLimitMetadata} from '@fluxer/schema/src/domains/common/ErrorSchemas';
type RateLimitScope = 'global' | 'shared' | 'user';
@@ -63,7 +63,7 @@ export class RateLimitError extends ThrottledError {
? safeRetryAfterDecimal
: resetAfterDecimal;
const safeScope = sanitizeRateLimitScope(scope, global);
const data: FluxerErrorData = {
const data: RateLimitMetadata = {
global,
retry_after: safeRetryAfterDecimal,
};
@@ -1,9 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {ValidationErrorCode} from '@fluxer/constants/src/ValidationErrorCodes';
import type {ValidationErrorItem} from '@fluxer/schema/src/domains/common/ErrorSchemas';
export interface ValidationError {
path: string;
message: string;
export interface ValidationError extends ValidationErrorItem {
code?: ValidationErrorCode;
}
@@ -360,9 +360,6 @@ export const ErrorCodeToI18nKey = {
[ValidationErrorCodes.FAILED_TO_UPLOAD_IMAGE]: 'attachments_and_uploads.failed_to_upload_image',
[ValidationErrorCodes.FILE_INDEX_EXCEEDS_MAXIMUM]: 'attachments_and_uploads.file_index_exceeds_maximum',
[ValidationErrorCodes.FILE_NOT_FOUND]: 'attachments_and_uploads.file_not_found',
[ValidationErrorCodes.FILENAME_EMPTY_AFTER_NORMALIZATION]:
'attachments_and_uploads.filename_empty_after_normalization',
[ValidationErrorCodes.FILENAME_INVALID_CHARACTERS]: 'attachments_and_uploads.filename_invalid_characters',
[ValidationErrorCodes.FILENAME_LENGTH_INVALID]: 'attachments_and_uploads.filename_length_invalid',
[ValidationErrorCodes.IMAGE_SIZE_EXCEEDS_LIMIT]: 'attachments_and_uploads.image_size_exceeds_limit',
[ValidationErrorCodes.INVALID_FILE_FIELD_NAME]: 'attachments_and_uploads.invalid_file_field_name',
@@ -455,8 +452,6 @@ export const ErrorCodeToI18nKey = {
[ValidationErrorCodes.CHANNEL_ID_IS_REQUIRED]: 'channels_and_guilds.channel_id_required',
[ValidationErrorCodes.CHANNEL_MUST_BE_DM_OR_GROUP_DM]: 'channels_and_guilds.channel_must_be_dm_or_group_dm',
[ValidationErrorCodes.CHANNEL_MUST_BE_VOICE]: 'channels_and_guilds.channel_must_be_voice',
[ValidationErrorCodes.CHANNEL_NAME_EMPTY_AFTER_NORMALIZATION]:
'names_and_normalization.channel_name_empty_after_normalization',
[ValidationErrorCodes.CATEGORIES_CANNOT_HAVE_PARENT_CHANNEL]:
'channels_and_guilds.categories_cannot_have_parent_channel',
[ValidationErrorCodes.CATEGORIES_CANNOT_HAVE_PARENTS]: 'channels_and_guilds.categories_cannot_have_parents',
@@ -5,7 +5,8 @@ import * as path from 'node:path';
import {fileURLToPath} from 'node:url';
import {getErrorMessageResult} from '@fluxer/errors/src/i18n/ErrorI18n';
import {ERROR_I18N_LOCALE_MESSAGES} from '@fluxer/errors/src/i18n/ErrorI18nLocales';
import {ERROR_I18N_MESSAGES} from '@fluxer/errors/src/i18n/ErrorI18nMessages';
import {ERROR_I18N_MESSAGES, type ErrorI18nKey} from '@fluxer/errors/src/i18n/ErrorI18nMessages';
import type {I18nResult} from '@fluxer/i18n/src/runtime/I18nTypes';
import {extractMessageTemplateVariables} from '@fluxer/i18n/src/runtime/MessageCatalogTypes';
import {parse, type Token} from '@messageformat/parser';
import {describe, expect, it} from 'vitest';
@@ -28,7 +29,7 @@ type ErrorMessageProbe = (
key: string,
locale: string,
variables: Record<string, number>,
) => ReturnType<typeof getErrorMessageResult>;
) => I18nResult<ErrorI18nKey, string>;
const probeErrorMessage = getErrorMessageResult as unknown as ErrorMessageProbe;
@@ -72,8 +72,6 @@ export const ERROR_I18N_MESSAGES = {
'File index {index} exceeds the maximum allowed index of {maxIndex}.',
'attachments_and_uploads.file_not_found': 'File not found.',
'attachments_and_uploads.file_size_too_large': 'File size is too large. Maximum file size is {maxSize}.',
'attachments_and_uploads.filename_empty_after_normalization': "Filename can't be empty after normalization.",
'attachments_and_uploads.filename_invalid_characters': 'Filename contains invalid characters.',
'attachments_and_uploads.filename_length_invalid': 'Filename must be between {min} and {max} characters.',
'attachments_and_uploads.image_size_exceeds_limit': 'Image size exceeds {maxSize} bytes.',
'attachments_and_uploads.invalid_file_field_name':
@@ -425,7 +423,6 @@ export const ERROR_I18N_MESSAGES = {
'You need to complete your account setup before you can submit reports.',
'moderation_and_reports.unknown_report': 'Unknown report.',
'moderation_and_reports.user_is_not_banned': "This user isn't banned.",
'names_and_normalization.channel_name_empty_after_normalization': "Channel name can't be empty after normalization.",
'names_and_normalization.name_empty_after_normalization': "Name can't be empty after normalization.",
'permissions.global_ip_banned':
'Your IP address {ipAddress} has been permanently blocked from the Fluxer API by platform administrators. If you believe this is a mistake, contact [email protected] to appeal. Include this IP address in your appeal.',
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_AR_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "فهرس الملف {index} يتجاوز الحد الأقصى المسموح به وهو {maxIndex}.",
"attachments_and_uploads.file_not_found": "لم يتم العثور على الملف.",
"attachments_and_uploads.file_size_too_large": "حجم الملف كبير جدًا. الحد الأقصى {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "لا يمكن أن يكون اسم الملف فارغًا بعد التسوية.",
"attachments_and_uploads.filename_invalid_characters": "اسم الملف يحتوي على أحرف غير صالحة.",
"attachments_and_uploads.filename_length_invalid": "يجب أن يكون اسم الملف بين {min} و {max} حرفًا.",
"attachments_and_uploads.image_size_exceeds_limit": "حجم الصورة يتجاوز {maxSize} بايت.",
"attachments_and_uploads.invalid_file_field_name": "اسم حقل ملف غير صالح: {key}. التنسيق المتوقع: `files[N]` حيث N هو رقم.",
@@ -372,7 +370,6 @@ const ERROR_I18N_AR_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "تحتاج إلى إكمال إعداد حسابك قبل إرسال التقارير.",
"moderation_and_reports.unknown_report": "تقرير غير معروف.",
"moderation_and_reports.user_is_not_banned": "المستخدم غير محظور.",
"names_and_normalization.channel_name_empty_after_normalization": "لا يمكن أن يكون اسم القناة فارغًا بعد التسوية.",
"names_and_normalization.name_empty_after_normalization": "لا يمكن أن يكون الاسم فارغًا بعد التسوية.",
"permissions.global_ip_banned": "تم حظر عنوان IP الخاص بك {ipAddress} نهائيا من Fluxer API بواسطة مسؤولي المنصة. إذا كنت تعتقد أن هذا خطأ، فتواصل مع [email protected] للاستئناف. اذكر عنوان IP هذا في الاستئناف.",
"permissions.global_ip_temporarily_banned": "تم حظر عنوان IP الخاص بك {ipAddress} مؤقتا من Fluxer API لمدة 24 ساعة بسبب أنماط وصول مسيئة أو غير معتادة. لا نوفر عادة استئنافات لحظر API المؤقت. غيّر عنوان IP أو انتظر انتهاء الحظر، وراجع أنماط الوصول إلى Fluxer API القادمة من عميلك.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_BG_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Файловият индекс {index} надвишава максимално допустимия индекс от {maxIndex}.",
"attachments_and_uploads.file_not_found": "Файлът не е намерен.",
"attachments_and_uploads.file_size_too_large": "Размерът на файла е твърде голям. Максималният размер е {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Името на файла не може да е празно след нормализация.",
"attachments_and_uploads.filename_invalid_characters": "Името на файла съдържа невалидни символи.",
"attachments_and_uploads.filename_length_invalid": "Името на файла трябва да е между {min} и {max} символа.",
"attachments_and_uploads.image_size_exceeds_limit": "Размерът на изображението е над {maxSize} байта.",
"attachments_and_uploads.invalid_file_field_name": "Невалидно име на поле за файл: {key}. Очакван формат: `files[N]`, където N е число.",
@@ -372,7 +370,6 @@ const ERROR_I18N_BG_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Трябва да завършиш акаунта си, преди да можеш да подаваш доклади.",
"moderation_and_reports.unknown_report": "Неизвестен доклад.",
"moderation_and_reports.user_is_not_banned": "Този потребител не е баннат.",
"names_and_normalization.channel_name_empty_after_normalization": "Името на канала не може да бъде празно след нормализиране.",
"names_and_normalization.name_empty_after_normalization": "Името не може да бъде празно след нормализиране.",
"permissions.global_ip_banned": "Вашият IP адрес {ipAddress} е блокиран за постоянно от Fluxer API от администраторите на платформата. Ако смятате, че това е грешка, свържете се със [email protected], за да обжалвате. Включете този IP адрес в обжалването.",
"permissions.global_ip_temporarily_banned": "Вашият IP адрес {ipAddress} е временно блокиран от Fluxer API за 24 часа поради злоупотребяващи или необичайни модели на достъп. Обикновено не предлагаме обжалвания за временни API блокировки. Сменете IP адреса или изчакайте блокировката да изтече и прегледайте моделите на достъп до Fluxer API от вашия клиент.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_CS_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Index souboru {index} překračuje maximální povolený index {maxIndex}.",
"attachments_and_uploads.file_not_found": "Soubor nebyl nalezen.",
"attachments_and_uploads.file_size_too_large": "Velikost souboru je příliš velká. Maximální velikost souboru je {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Název souboru nemůže být po normalizaci prázdný.",
"attachments_and_uploads.filename_invalid_characters": "Název souboru obsahuje neplatné znaky.",
"attachments_and_uploads.filename_length_invalid": "Název souboru musí být dlouhý {min} až {max} znaků.",
"attachments_and_uploads.image_size_exceeds_limit": "Velikost obrázku přesahuje {maxSize} bajtů.",
"attachments_and_uploads.invalid_file_field_name": "Neplatný název pole souboru: {key}. Očekávaný formát je `files[N]`, kde N je číslo.",
@@ -372,7 +370,6 @@ const ERROR_I18N_CS_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Než budeš moci odesílat hlášení, musíš nejdřív dokončit nastavení svého účtu.",
"moderation_and_reports.unknown_report": "Neznámé hlášení.",
"moderation_and_reports.user_is_not_banned": "Tento uživatel není zabanován.",
"names_and_normalization.channel_name_empty_after_normalization": "Název kanálu nemůže být po normalizaci prázdný.",
"names_and_normalization.name_empty_after_normalization": "Název nemůže být po normalizaci prázdný.",
"permissions.global_ip_banned": "Vaše IP adresa {ipAddress} byla správci platformy trvale zablokována pro Fluxer API. Pokud si myslíte, že jde o chybu, kontaktujte [email protected] a požádejte o přezkum. Uveďte tuto IP adresu v žádosti.",
"permissions.global_ip_temporarily_banned": "Vaše IP adresa {ipAddress} byla dočasně zablokována pro Fluxer API na 24 hodin kvůli zneužívajícím nebo neobvyklým vzorcům přístupu. U dočasných zákazů API obvykle nenabízíme odvolání. Změňte IP adresu nebo počkejte na vypršení zákazu a zkontrolujte vzorce přístupu k Fluxer API z vašeho klienta.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_DA_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Filindeks {index} overskrider det maksimalt tilladte indeks på {maxIndex}.",
"attachments_and_uploads.file_not_found": "Fil ikke fundet.",
"attachments_and_uploads.file_size_too_large": "Filstørrelsen er for stor. Maksimal filstørrelse er {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Filnavnet kan ikke være tomt efter normalisering.",
"attachments_and_uploads.filename_invalid_characters": "Filnavnet indeholder ugyldige tegn.",
"attachments_and_uploads.filename_length_invalid": "Filnavnet skal være mellem {min} og {max} tegn.",
"attachments_and_uploads.image_size_exceeds_limit": "Billedstørrelsen overskrider {maxSize} bytes.",
"attachments_and_uploads.invalid_file_field_name": "Ugyldigt filfeltnavn: {key}. Forventet format: `files[N]`, hvor N er et tal.",
@@ -372,7 +370,6 @@ const ERROR_I18N_DA_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Du skal fuldføre din kontoopsætning, før du kan indsende rapporter.",
"moderation_and_reports.unknown_report": "Ukendt rapport.",
"moderation_and_reports.user_is_not_banned": "Denne bruger er ikke bandlyst.",
"names_and_normalization.channel_name_empty_after_normalization": "Kanalnavnet må ikke være tomt efter normalisering.",
"names_and_normalization.name_empty_after_normalization": "Navnet må ikke være tomt efter normalisering.",
"permissions.global_ip_banned": "Din IP-adresse {ipAddress} er permanent blokeret fra Fluxer API af platformadministratorer. Hvis du mener, at dette er en fejl, kan du kontakte [email protected] for at klage. Medtag denne IP-adresse i klagen.",
"permissions.global_ip_temporarily_banned": "Din IP-adresse {ipAddress} er midlertidigt blokeret fra Fluxer API i 24 timer på grund af misbrugende eller usædvanlige adgangsmønstre. Vi tilbyder normalt ikke klager for midlertidige API-bans. Skift IP-adresse eller vent på, at blokeringen udløber, og gennemgå adgangsmønstrene til Fluxer API fra din klient.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_DE_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Der Dateiindex {index} überschreitet den maximal zulässigen Index von {maxIndex}.",
"attachments_and_uploads.file_not_found": "Datei nicht gefunden.",
"attachments_and_uploads.file_size_too_large": "Die Dateigröße ist zu groß. Die maximale Dateigröße beträgt {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Der Dateiname darf nach der Normalisierung nicht leer sein.",
"attachments_and_uploads.filename_invalid_characters": "Der Dateiname enthält ungültige Zeichen.",
"attachments_and_uploads.filename_length_invalid": "Der Dateiname muss zwischen {min} und {max} Zeichen lang sein.",
"attachments_and_uploads.image_size_exceeds_limit": "Die Bildgröße überschreitet {maxSize} Bytes.",
"attachments_and_uploads.invalid_file_field_name": "Ungültiger Dateifeldname: {key}. Erwartetes Format: `files[N]`, wobei N eine Zahl ist.",
@@ -372,7 +370,6 @@ const ERROR_I18N_DE_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Du musst deinen Account einrichten, bevor du Meldungen absenden kannst.",
"moderation_and_reports.unknown_report": "Unbekannte Meldung.",
"moderation_and_reports.user_is_not_banned": "Dieser Benutzer ist nicht gebannt.",
"names_and_normalization.channel_name_empty_after_normalization": "Der Kanalname kann nach der Normalisierung nicht leer sein.",
"names_and_normalization.name_empty_after_normalization": "Der Name darf nach der Normalisierung nicht leer bleiben.",
"permissions.global_ip_banned": "Deine IP-Adresse {ipAddress} wurde von Plattformadministratoren dauerhaft von der Fluxer API gesperrt. Wenn du glaubst, dass dies ein Fehler ist, kontaktiere [email protected], um Einspruch einzulegen. Gib diese IP-Adresse in deinem Einspruch an.",
"permissions.global_ip_temporarily_banned": "Deine IP-Adresse {ipAddress} wurde wegen missbräuchlicher oder ungewöhnlicher Zugriffsmuster vorübergehend für 24 Stunden von der Fluxer API gesperrt. Für temporäre API-Sperren bieten wir normalerweise keine Einsprüche an. Wechsle die IP-Adresse oder warte, bis die Sperre abläuft, und überprüfe die Zugriffsmuster deines Clients auf die Fluxer API.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_EL_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Το ευρετήριο αρχείου {index} ξεπερνά το μέγιστο επιτρεπόμενο {maxIndex}.",
"attachments_and_uploads.file_not_found": "Το αρχείο δεν βρέθηκε.",
"attachments_and_uploads.file_size_too_large": "Το μέγεθος του αρχείου είναι πολύ μεγάλο. Το μέγιστο μέγεθος αρχείου είναι {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Το όνομα αρχείου δεν μπορεί να είναι άδειο μετά την κανονικοποίηση.",
"attachments_and_uploads.filename_invalid_characters": "Το όνομα αρχείου περιέχει μη έγκυρους χαρακτήρες.",
"attachments_and_uploads.filename_length_invalid": "Το όνομα αρχείου πρέπει να έχει μεταξύ {min} και {max} χαρακτήρες.",
"attachments_and_uploads.image_size_exceeds_limit": "Το μέγεθος της εικόνας ξεπερνά τα {maxSize} bytes.",
"attachments_and_uploads.invalid_file_field_name": "Μη έγκυρο όνομα πεδίου αρχείου: {key}. Η αναμενόμενη μορφή είναι: `files[N]` όπου N είναι ένας αριθμός.",
@@ -372,7 +370,6 @@ const ERROR_I18N_EL_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Πρέπει να ολοκληρώσεις τη ρύθμιση του λογαριασμού σου πριν υποβάλεις αναφορές.",
"moderation_and_reports.unknown_report": "Δεν βρέθηκε η αναφορά.",
"moderation_and_reports.user_is_not_banned": "Αυτός ο χρήστης δεν έχει αποκλειστεί.",
"names_and_normalization.channel_name_empty_after_normalization": "Το όνομα του καναλιού δεν μπορεί να είναι κενό μετά την κανονικοποίηση.",
"names_and_normalization.name_empty_after_normalization": "Το όνομα δεν μπορεί να είναι κενό μετά την κανονικοποίηση.",
"permissions.global_ip_banned": "Η διεύθυνση IP σου {ipAddress} έχει αποκλειστεί μόνιμα από το Fluxer API από τους διαχειριστές της πλατφόρμας. Αν πιστεύεις ότι πρόκειται για λάθος, επικοινώνησε με [email protected] για ένσταση. Συμπερίλαβε αυτήν τη διεύθυνση IP στην ένστασή σου.",
"permissions.global_ip_temporarily_banned": "Η διεύθυνση IP σου {ipAddress} έχει αποκλειστεί προσωρινά από το Fluxer API για 24 ώρες λόγω καταχρηστικών ή ασυνήθιστων μοτίβων πρόσβασης. Συνήθως δεν παρέχουμε ενστάσεις για προσωρινούς αποκλεισμούς API. Άλλαξε διεύθυνση IP ή περίμενε να λήξει ο αποκλεισμός και έλεγξε τα μοτίβα πρόσβασης στο Fluxer API από τον client σου.",
@@ -63,8 +63,6 @@ const ERROR_I18N_EN_GB_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "File index {index} exceeds the maximum allowed index of {maxIndex}.",
"attachments_and_uploads.file_not_found": "File not found.",
"attachments_and_uploads.file_size_too_large": "File size is too large. Maximum file size is {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Filename can't be empty after normalization.",
"attachments_and_uploads.filename_invalid_characters": "Filename contains invalid characters.",
"attachments_and_uploads.filename_length_invalid": "Filename must be between {min} and {max} characters.",
"attachments_and_uploads.image_size_exceeds_limit": "Image size exceeds {maxSize} bytes.",
"attachments_and_uploads.invalid_file_field_name": "Invalid file field name: {key}. Expected format: `files[N]` where N is a number.",
@@ -372,7 +370,6 @@ const ERROR_I18N_EN_GB_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "You need to complete your account setup before you can submit reports.",
"moderation_and_reports.unknown_report": "Unknown report.",
"moderation_and_reports.user_is_not_banned": "This user isn't banned.",
"names_and_normalization.channel_name_empty_after_normalization": "Channel name can't be empty after normalisation.",
"names_and_normalization.name_empty_after_normalization": "Name can't be empty after normalisation.",
"permissions.global_ip_banned": "Your IP address {ipAddress} has been permanently blocked from the Fluxer API by platform administrators. If you believe this is a mistake, contact [email protected] to appeal. Include this IP address in your appeal.",
"permissions.global_ip_temporarily_banned": "Your IP address {ipAddress} has been temporarily blocked from the Fluxer API for 24 hours because of abusive or unusual access patterns. We usually do not provide appeals for temporary API bans. Change IP addresses or wait for the ban to expire, and review the Fluxer API access patterns coming from your client.",
@@ -63,8 +63,6 @@ const ERROR_I18N_ES_419_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "El índice de archivo {index} excede el índice máximo permitido de {maxIndex}.",
"attachments_and_uploads.file_not_found": "Archivo no encontrado.",
"attachments_and_uploads.file_size_too_large": "El tamaño del archivo es demasiado grande. El tamaño máximo del archivo es {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "El nombre de archivo no puede estar vacío después de la normalización.",
"attachments_and_uploads.filename_invalid_characters": "El nombre de archivo contiene caracteres no válidos.",
"attachments_and_uploads.filename_length_invalid": "El nombre de archivo debe tener entre {min} y {max} caracteres.",
"attachments_and_uploads.image_size_exceeds_limit": "El tamaño de la imagen excede los {maxSize} bytes.",
"attachments_and_uploads.invalid_file_field_name": "Nombre de campo de archivo no válido: {key}. Formato esperado: `files[N]` donde N es un número.",
@@ -372,7 +370,6 @@ const ERROR_I18N_ES_419_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Debes completar la configuración de tu cuenta antes de poder enviar denuncias.",
"moderation_and_reports.unknown_report": "Denuncia desconocida.",
"moderation_and_reports.user_is_not_banned": "Este usuario no ha sido baneado.",
"names_and_normalization.channel_name_empty_after_normalization": "El nombre del canal no puede quedar vacío después de la normalización.",
"names_and_normalization.name_empty_after_normalization": "El nombre no puede quedar vacío después de la normalización.",
"permissions.global_ip_banned": "Tu dirección IP {ipAddress} fue bloqueada permanentemente de la API de Fluxer por administradores de la plataforma. Si crees que es un error, escribe a [email protected] para apelar. Incluye esta dirección IP en tu apelación.",
"permissions.global_ip_temporarily_banned": "Tu dirección IP {ipAddress} fue bloqueada temporalmente de la API de Fluxer durante 24 horas por patrones de acceso abusivos o inusuales. Por lo general no ofrecemos apelaciones para bloqueos temporales de la API. Cambia de dirección IP o espera a que termine el bloqueo, y revisa los patrones de acceso a la API de Fluxer que provienen de tu cliente.",
@@ -63,8 +63,6 @@ const ERROR_I18N_ES_ES_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "El índice de archivo {index} supera el máximo permitido de {maxIndex}.",
"attachments_and_uploads.file_not_found": "Archivo no encontrado.",
"attachments_and_uploads.file_size_too_large": "El archivo es demasiado grande. El tamaño máximo de archivo es {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "El nombre del archivo no puede estar vacío después de la normalización.",
"attachments_and_uploads.filename_invalid_characters": "El nombre del archivo contiene caracteres no válidos.",
"attachments_and_uploads.filename_length_invalid": "El nombre del archivo debe tener entre {min} y {max} caracteres.",
"attachments_and_uploads.image_size_exceeds_limit": "El tamaño de la imagen supera los {maxSize} bytes.",
"attachments_and_uploads.invalid_file_field_name": "Nombre de campo de archivo no válido: {key}. Formato esperado: `files[N]` donde N es un número.",
@@ -372,7 +370,6 @@ const ERROR_I18N_ES_ES_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Necesitas completar la configuración de tu cuenta antes de poder enviar informes.",
"moderation_and_reports.unknown_report": "Informe desconocido.",
"moderation_and_reports.user_is_not_banned": "Este usuario no está baneado.",
"names_and_normalization.channel_name_empty_after_normalization": "El nombre del canal no puede quedar vacío tras la normalización.",
"names_and_normalization.name_empty_after_normalization": "El nombre no puede quedar vacío tras la normalización.",
"permissions.global_ip_banned": "Tu dirección IP {ipAddress} ha sido bloqueada permanentemente de la API de Fluxer por administradores de la plataforma. Si crees que es un error, contacta con [email protected] para apelar. Incluye esta dirección IP en la apelación.",
"permissions.global_ip_temporarily_banned": "Tu dirección IP {ipAddress} ha sido bloqueada temporalmente de la API de Fluxer durante 24 horas por patrones de acceso abusivos o inusuales. Normalmente no ofrecemos apelaciones para baneos temporales de la API. Cambia de dirección IP o espera a que expire el bloqueo, y revisa los patrones de acceso a la API de Fluxer que provienen de tu cliente.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_FI_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Tiedoston indeksi {index} ylittää suurimman sallitun indeksin {maxIndex}.",
"attachments_and_uploads.file_not_found": "Tiedostoa ei löytynyt.",
"attachments_and_uploads.file_size_too_large": "Tiedoston koko on liian suuri. Suurin sallittu koko on {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Tiedostonimi ei saa olla tyhjä normalisoinnin jälkeen.",
"attachments_and_uploads.filename_invalid_characters": "Tiedostonimi sisältää virheellisiä merkkejä.",
"attachments_and_uploads.filename_length_invalid": "Tiedostonimen on oltava {min}–{max} merkkiä pitkä.",
"attachments_and_uploads.image_size_exceeds_limit": "Kuvan koko on yli {maxSize} tavua.",
"attachments_and_uploads.invalid_file_field_name": "Virheellinen tiedostokentän nimi: {key}. Odotettu muoto: `files[N]`, jossa N on numero.",
@@ -372,7 +370,6 @@ const ERROR_I18N_FI_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Viimeistele tilisi asetukset, ennen kuin voit tehdä ilmiantoja.",
"moderation_and_reports.unknown_report": "Tuntematon ilmianto.",
"moderation_and_reports.user_is_not_banned": "Tätä käyttäjää ei ole porttikielletty.",
"names_and_normalization.channel_name_empty_after_normalization": "Normalisoinnin jälkeen kanavan nimi ei voi olla tyhjä.",
"names_and_normalization.name_empty_after_normalization": "Normalisoinnin jälkeen nimi ei voi olla tyhjä.",
"permissions.global_ip_banned": "IP-osoitteesi {ipAddress} on estetty pysyvästi Fluxer APIsta alustan ylläpitäjien toimesta. Jos uskot tämän olevan virhe, ota yhteyttä [email protected] tehdäksesi valituksen. Liitä tämä IP-osoite valitukseen.",
"permissions.global_ip_temporarily_banned": "IP-osoitteesi {ipAddress} on estetty väliaikaisesti Fluxer APIsta 24 tunniksi väärinkäyttöä muistuttavien tai epätavallisten käyttötapojen vuoksi. Emme yleensä tarjoa valituksia väliaikaisista API-estoista. Vaihda IP-osoitetta tai odota eston päättymistä ja tarkista asiakkaastasi tulevat Fluxer API -käyttötavat.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_FR_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "L'index de fichier {index} dépasse l'index maximal autorisé de {maxIndex}.",
"attachments_and_uploads.file_not_found": "Fichier introuvable.",
"attachments_and_uploads.file_size_too_large": "La taille du fichier est trop grande. La taille maximale du fichier est de {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Le nom de fichier ne peut pas être vide après normalisation.",
"attachments_and_uploads.filename_invalid_characters": "Le nom de fichier contient des caractères non valides.",
"attachments_and_uploads.filename_length_invalid": "Le nom de fichier doit contenir entre {min} et {max} caractères.",
"attachments_and_uploads.image_size_exceeds_limit": "La taille de l'image dépasse {maxSize} octets.",
"attachments_and_uploads.invalid_file_field_name": "Nom de champ de fichier non valide : {key}. Format attendu : `files[N]` où N est un nombre.",
@@ -372,7 +370,6 @@ const ERROR_I18N_FR_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Tu dois finaliser la configuration de ton compte avant de pouvoir soumettre des signalements.",
"moderation_and_reports.unknown_report": "Signalement inconnu.",
"moderation_and_reports.user_is_not_banned": "Cet utilisateur n'est pas banni.",
"names_and_normalization.channel_name_empty_after_normalization": "Le nom du salon ne peut pas être vide une fois normalisé.",
"names_and_normalization.name_empty_after_normalization": "Le nom ne peut pas être vide une fois normalisé.",
"permissions.global_ip_banned": "Ton adresse IP {ipAddress} a été définitivement bloquée de l'API Fluxer par les administrateurs de la plateforme. Si tu penses qu'il s'agit d'une erreur, contacte [email protected] pour faire appel. Indique cette adresse IP dans ton appel.",
"permissions.global_ip_temporarily_banned": "Ton adresse IP {ipAddress} a été temporairement bloquée de l'API Fluxer pendant 24 heures en raison de schémas d'accès abusifs ou inhabituels. Nous ne proposons généralement pas d'appel pour les bannissements temporaires de l'API. Change d'adresse IP ou attends l'expiration du bannissement, et vérifie les schémas d'accès à l'API Fluxer provenant de ton client.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_HE_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "אינדקס הקובץ {index} חורג מהאינדקס המקסימלי המותר {maxIndex}.",
"attachments_and_uploads.file_not_found": "קובץ לא נמצא.",
"attachments_and_uploads.file_size_too_large": "גודל הקובץ גדול מדי. הגודל המקסימלי הוא {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "שם הקובץ אינו יכול להיות ריק לאחר נורמליזציה.",
"attachments_and_uploads.filename_invalid_characters": "שם הקובץ מכיל תווים אסורים.",
"attachments_and_uploads.filename_length_invalid": "שם הקובץ חייב להיות באורך של {min} עד {max} תווים.",
"attachments_and_uploads.image_size_exceeds_limit": "גודל התמונה עולה על {maxSize} בתים.",
"attachments_and_uploads.invalid_file_field_name": "שם שדה קובץ לא חוקי: {key}. הפורמט הצפוי: `files[N]` כאשר N הוא מספר.",
@@ -372,7 +370,6 @@ const ERROR_I18N_HE_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "עליך להשלים את הגדרות החשבון שלך לפני הגשת דיווחים.",
"moderation_and_reports.unknown_report": "דיווח שגוי.",
"moderation_and_reports.user_is_not_banned": "משתמש זה לא חסום.",
"names_and_normalization.channel_name_empty_after_normalization": "שם הערוץ לא יכול להיות ריק לאחר נורמליזציה.",
"names_and_normalization.name_empty_after_normalization": "השם לא יכול להיות ריק לאחר נורמליזציה.",
"permissions.global_ip_banned": "כתובת ה-IP שלך {ipAddress} נחסמה לצמיתות מ-Fluxer API על ידי מנהלי הפלטפורמה. אם לדעתך זו טעות, אפשר לפנות אל [email protected] כדי לערער. יש לכלול את כתובת ה-IP הזו בערעור.",
"permissions.global_ip_temporarily_banned": "כתובת ה-IP שלך {ipAddress} נחסמה זמנית מ-Fluxer API למשך 24 שעות בגלל דפוסי גישה פוגעניים או חריגים. בדרך כלל איננו מאפשרים ערעורים על חסימות API זמניות. החלף כתובת IP או המתן לסיום החסימה, ובדוק את דפוסי הגישה ל-Fluxer API שמגיעים מהלקוח שלך.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_HI_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "फाइल इंडेक्स {index} अधिकतम अनुमत इंडेक्स {maxIndex} से अधिक है।",
"attachments_and_uploads.file_not_found": "फाइल नहीं मिली।",
"attachments_and_uploads.file_size_too_large": "फाइल का साइज बहुत ज़्यादा है। अधिकतम फाइल साइज {maxSize} है।",
"attachments_and_uploads.filename_empty_after_normalization": "नॉर्मलाइज़ेशन के बाद फाइलनेम खाली नहीं हो सकता।",
"attachments_and_uploads.filename_invalid_characters": "फाइलनेम में अमान्य अक्षर हैं।",
"attachments_and_uploads.filename_length_invalid": "फाइलनेम {min} और {max} अक्षर के बीच में होना चाहिए।",
"attachments_and_uploads.image_size_exceeds_limit": "इमेज का साइज {maxSize} बाइट्स से अधिक है।",
"attachments_and_uploads.invalid_file_field_name": "अमान्य फाइल फील्ड नेम: {key}। सही फॉर्मेट: `files[N]` जहाँ N एक नंबर है।",
@@ -372,7 +370,6 @@ const ERROR_I18N_HI_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "रिपोर्ट सबमिट करने से पहले आपको अपना अकाउंट सेटअप पूरा करना होगा।",
"moderation_and_reports.unknown_report": "अज्ञात रिपोर्ट।",
"moderation_and_reports.user_is_not_banned": "यह यूज़र बैन नहीं है।",
"names_and_normalization.channel_name_empty_after_normalization": "नॉर्मलाइज़ेशन के बाद चैनल का नाम खाली नहीं हो सकता।",
"names_and_normalization.name_empty_after_normalization": "नॉर्मलाइज़ेशन के बाद नाम खाली नहीं हो सकता।",
"permissions.global_ip_banned": "आपका IP पता {ipAddress} प्लेटफ़ॉर्म एडमिनों द्वारा Fluxer API से स्थायी रूप से ब्लॉक कर दिया गया है। अगर आपको लगता है कि यह गलती है, तो अपील के लिए [email protected] से संपर्क करें। अपनी अपील में यह IP पता शामिल करें।",
"permissions.global_ip_temporarily_banned": "आपका IP पता {ipAddress} दुर्व्यवहारपूर्ण या असामान्य access patterns के कारण Fluxer API से 24 घंटे के लिए अस्थायी रूप से ब्लॉक कर दिया गया है। अस्थायी API bans के लिए हम आम तौर पर appeals नहीं देते। IP पता बदलें या ban समाप्त होने तक प्रतीक्षा करें, और अपने client से Fluxer API पर आने वाले access patterns की समीक्षा करें।",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_HR_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Indeks datoteke {index} premašuje maksimalno dopušteni indeks od {maxIndex}.",
"attachments_and_uploads.file_not_found": "Datoteka nije pronađena.",
"attachments_and_uploads.file_size_too_large": "Veličina datoteke je prevelika. Maksimalna veličina datoteke je {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Naziv datoteke ne može biti prazan nakon normalizacije.",
"attachments_and_uploads.filename_invalid_characters": "Naziv datoteke sadrži nevažeće znakove.",
"attachments_and_uploads.filename_length_invalid": "Naziv datoteke mora imati između {min} i {max} znakova.",
"attachments_and_uploads.image_size_exceeds_limit": "Veličina slike premašuje {maxSize} bajtova.",
"attachments_and_uploads.invalid_file_field_name": "Nevažeći naziv polja datoteke: {key}. Očekivani format: `files[N]` gdje je N broj.",
@@ -372,7 +370,6 @@ const ERROR_I18N_HR_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Moraš dovršiti postavljanje računa prije nego što možeš podnositi izvješća.",
"moderation_and_reports.unknown_report": "Nepoznato izvješće.",
"moderation_and_reports.user_is_not_banned": "Ovaj korisnik nije banan.",
"names_and_normalization.channel_name_empty_after_normalization": "Naziv kanala ne može biti prazan poslije normalizacije.",
"names_and_normalization.name_empty_after_normalization": "Ime ne može biti prazno poslije normalizacije.",
"permissions.global_ip_banned": "Tvoja IP adresa {ipAddress} trajno je blokirana za Fluxer API od strane administratora platforme. Ako smatraš da je to pogreška, kontaktiraj [email protected] radi žalbe. Uključi ovu IP adresu u žalbu.",
"permissions.global_ip_temporarily_banned": "Tvoja IP adresa {ipAddress} privremeno je blokirana za Fluxer API na 24 sata zbog zloupotrebljavajućih ili neuobičajenih obrazaca pristupa. Obično ne nudimo žalbe za privremene API banove. Promijeni IP adresu ili pričekaj da ban istekne i pregledaj obrasce pristupa Fluxer API-ju koji dolaze iz tvog klijenta.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_HU_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "A(z) {index}. fájl indexe meghaladja a maximálisan engedélyezett {maxIndex} indexet.",
"attachments_and_uploads.file_not_found": "A fájl nem található.",
"attachments_and_uploads.file_size_too_large": "A fájl mérete túl nagy. A megengedett maximális fájlméret {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "A fájlnév nem maradhat üresen a normalizálás után.",
"attachments_and_uploads.filename_invalid_characters": "A fájlnév érvénytelen karaktereket tartalmaz.",
"attachments_and_uploads.filename_length_invalid": "A fájlnévnek {min} és {max} karakter között kell lennie.",
"attachments_and_uploads.image_size_exceeds_limit": "A kép mérete túl nagy, maximum {maxSize} bájt lehet.",
"attachments_and_uploads.invalid_file_field_name": "Érvénytelen fájlmezőnév: {key}. Elvárt formátum: `files[N]`, ahol N egy szám.",
@@ -372,7 +370,6 @@ const ERROR_I18N_HU_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Fejezd be a fiókod beállítását, mielőtt jelentéseket küldhetnél.",
"moderation_and_reports.unknown_report": "Ismeretlen jelentés.",
"moderation_and_reports.user_is_not_banned": "Ez a felhasználó nincs kitiltva.",
"names_and_normalization.channel_name_empty_after_normalization": "Normalizálás után a csatornanév nem lehet üres.",
"names_and_normalization.name_empty_after_normalization": "Normalizálás után a név nem lehet üres.",
"permissions.global_ip_banned": "Az IP-címedet ({ipAddress}) a platform adminisztrátorai véglegesen letiltották a Fluxer API-ról. Ha úgy gondolod, hogy ez hiba, írj a [email protected] címre fellebbezéshez. Add meg ezt az IP-címet a fellebbezésben.",
"permissions.global_ip_temporarily_banned": "Az IP-címedet ({ipAddress}) visszaélésszerű vagy szokatlan hozzáférési minták miatt ideiglenesen, 24 órára letiltottuk a Fluxer API-ról. Ideiglenes API-tiltásokhoz általában nem biztosítunk fellebbezést. Válts IP-címet vagy várd meg a tiltás lejártát, és ellenőrizd a kliensedből érkező Fluxer API hozzáférési mintákat.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_ID_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Indeks file {index} melebihi indeks maksimum yang diizinkan yaitu {maxIndex}.",
"attachments_and_uploads.file_not_found": "File tidak ditemukan.",
"attachments_and_uploads.file_size_too_large": "Ukuran file terlalu besar. Ukuran file maksimum adalah {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Nama file tidak boleh kosong setelah dinormalisasi.",
"attachments_and_uploads.filename_invalid_characters": "Nama file berisi karakter yang tidak valid.",
"attachments_and_uploads.filename_length_invalid": "Panjang nama file harus antara {min} dan {max} karakter.",
"attachments_and_uploads.image_size_exceeds_limit": "Ukuran gambar melebihi {maxSize} byte.",
"attachments_and_uploads.invalid_file_field_name": "Nama kolom file tidak valid: {key}. Format yang benar: `files[N]` dengan N adalah angka.",
@@ -372,7 +370,6 @@ const ERROR_I18N_ID_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Kamu perlu menyelesaikan penyiapan akunmu sebelum bisa mengirim laporan.",
"moderation_and_reports.unknown_report": "Laporan tidak dikenal.",
"moderation_and_reports.user_is_not_banned": "Pengguna ini tidak di-banned.",
"names_and_normalization.channel_name_empty_after_normalization": "Nama kanal tidak bisa kosong setelah normalisasi.",
"names_and_normalization.name_empty_after_normalization": "Nama tidak bisa kosong setelah normalisasi.",
"permissions.global_ip_banned": "Alamat IP kamu {ipAddress} telah diblokir permanen dari Fluxer API oleh admin platform. Jika kamu merasa ini keliru, hubungi [email protected] untuk mengajukan banding. Sertakan alamat IP ini dalam banding kamu.",
"permissions.global_ip_temporarily_banned": "Alamat IP kamu {ipAddress} diblokir sementara dari Fluxer API selama 24 jam karena pola akses yang abusif atau tidak biasa. Biasanya kami tidak menyediakan banding untuk ban API sementara. Ganti alamat IP atau tunggu sampai ban berakhir, dan tinjau pola akses ke Fluxer API yang berasal dari klien kamu.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_IT_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "L'indice del file {index} supera l'indice massimo consentito di {maxIndex}.",
"attachments_and_uploads.file_not_found": "File non trovato.",
"attachments_and_uploads.file_size_too_large": "La dimensione del file è troppo grande. La dimensione massima del file è {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Il nome del file non può essere vuoto dopo la normalizzazione.",
"attachments_and_uploads.filename_invalid_characters": "Il nome del file contiene caratteri non validi.",
"attachments_and_uploads.filename_length_invalid": "Il nome del file deve essere compreso tra {min} e {max} caratteri.",
"attachments_and_uploads.image_size_exceeds_limit": "La dimensione dell'immagine supera i {maxSize} byte.",
"attachments_and_uploads.invalid_file_field_name": "Nome del campo file non valido: {key}. Formato previsto: `files[N]` dove N è un numero.",
@@ -372,7 +370,6 @@ const ERROR_I18N_IT_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Devi completare la configurazione dell'account prima di inviare segnalazioni.",
"moderation_and_reports.unknown_report": "Segnalazione sconosciuta.",
"moderation_and_reports.user_is_not_banned": "L'utente non è bannato.",
"names_and_normalization.channel_name_empty_after_normalization": "Il nome del canale non può essere vuoto dopo la normalizzazione.",
"names_and_normalization.name_empty_after_normalization": "Il nome non può essere vuoto dopo la normalizzazione.",
"permissions.global_ip_banned": "Il tuo indirizzo IP {ipAddress} è stato bloccato in modo permanente dall'API Fluxer dagli amministratori della piattaforma. Se pensi che sia un errore, contatta [email protected] per fare ricorso. Includi questo indirizzo IP nel ricorso.",
"permissions.global_ip_temporarily_banned": "Il tuo indirizzo IP {ipAddress} è stato bloccato temporaneamente dall'API Fluxer per 24 ore a causa di schemi di accesso abusivi o insoliti. Di solito non offriamo ricorsi per i ban temporanei dell'API. Cambia indirizzo IP o attendi la scadenza del ban, e controlla gli schemi di accesso all'API Fluxer provenienti dal tuo client.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_JA_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "ファイルインデックス {index} が最大許容インデックス {maxIndex} を超えています。",
"attachments_and_uploads.file_not_found": "ファイルが見つかりません。",
"attachments_and_uploads.file_size_too_large": "ファイルサイズが大きすぎます。最大ファイルサイズは{maxSize}です。",
"attachments_and_uploads.filename_empty_after_normalization": "正規化後のファイル名は空にできません。",
"attachments_and_uploads.filename_invalid_characters": "ファイル名に無効な文字が含まれています。",
"attachments_and_uploads.filename_length_invalid": "ファイル名は{min}文字以上{max}文字以下で指定してください。",
"attachments_and_uploads.image_size_exceeds_limit": "画像サイズが{maxSize}バイトを超えています。",
"attachments_and_uploads.invalid_file_field_name": "無効なファイルフィールド名: {key}。`files[N]`(Nは数字)の形式にしてください。",
@@ -372,7 +370,6 @@ const ERROR_I18N_JA_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "レポートを送信する前に、アカウント設定を完了する必要があります。",
"moderation_and_reports.unknown_report": "不明なレポートです。",
"moderation_and_reports.user_is_not_banned": "このユーザーはBANされていません。",
"names_and_normalization.channel_name_empty_after_normalization": "正規化後、チャンネル名を空にできません。",
"names_and_normalization.name_empty_after_normalization": "正規化後、名前を空にできません。",
"permissions.global_ip_banned": "あなたのIPアドレス {ipAddress} は、プラットフォーム管理者によりFluxer APIから永久にブロックされました。誤りだと思う場合は、[email protected] に連絡して異議申し立てをしてください。申し立てにはこのIPアドレスを記載してください。",
"permissions.global_ip_temporarily_banned": "あなたのIPアドレス {ipAddress} は、不正または通常と異なるアクセスパターンのため、Fluxer APIから24時間一時的にブロックされました。一時的なAPI banについては通常、異議申し立てを受け付けていません。IPアドレスを変更するかbanの期限切れを待ち、クライアントからFluxer APIへのアクセスパターンを確認してください。",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_KO_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "파일 인덱스 {index}가 최대 인덱스 {maxIndex}를 초과했어요.",
"attachments_and_uploads.file_not_found": "파일을 찾을 수 없어요.",
"attachments_and_uploads.file_size_too_large": "파일 크기가 너무 커요. 최대 파일 크기는 {maxSize}이에요.",
"attachments_and_uploads.filename_empty_after_normalization": "정규화 후 파일 이름은 비어 있으면 안 돼요.",
"attachments_and_uploads.filename_invalid_characters": "파일 이름에 유효하지 않은 문자가 포함되어 있어요.",
"attachments_and_uploads.filename_length_invalid": "파일 이름은 {min}자 이상 {max}자 이하여야 해요.",
"attachments_and_uploads.image_size_exceeds_limit": "이미지 크기가 {maxSize}바이트를 초과했어요.",
"attachments_and_uploads.invalid_file_field_name": "유효하지 않은 파일 필드 이름: {key}. 예상 형식: `files[N]` (N은 숫자).",
@@ -372,7 +370,6 @@ const ERROR_I18N_KO_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "신고를 제출하려면 계정 설정을 완료해야 해요.",
"moderation_and_reports.unknown_report": "알 수 없는 신고예요.",
"moderation_and_reports.user_is_not_banned": "이 사용자는 차단되지 않았어요.",
"names_and_normalization.channel_name_empty_after_normalization": "정규화 후 채널 이름은 비워둘 수 없어요.",
"names_and_normalization.name_empty_after_normalization": "정규화 후 이름은 비워둘 수 없어요.",
"permissions.global_ip_banned": "회원님의 IP 주소 {ipAddress}는 플랫폼 관리자가 Fluxer API에서 영구 차단했습니다. 오류라고 생각한다면 [email protected]으로 문의해 이의를 제기하세요. 이의 제기에는 이 IP 주소를 포함하세요.",
"permissions.global_ip_temporarily_banned": "회원님의 IP 주소 {ipAddress}는 악용 또는 비정상적인 접근 패턴으로 인해 Fluxer API에서 24시간 동안 일시 차단되었습니다. 일시적인 API 차단에는 보통 이의 제기를 제공하지 않습니다. IP 주소를 변경하거나 차단이 만료될 때까지 기다리고, 클라이언트에서 Fluxer API로 발생하는 접근 패턴을 검토하세요.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_LT_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Failo indeksas {index} viršija didžiausią leistiną indeksą – {maxIndex}.",
"attachments_and_uploads.file_not_found": "Failas nerastas.",
"attachments_and_uploads.file_size_too_large": "Failo dydis per didelis. Didžiausias failo dydis yra {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Failo pavadinimas negali būti tuščias po normalizavimo.",
"attachments_and_uploads.filename_invalid_characters": "Failo pavadinime yra neleistinų simbolių.",
"attachments_and_uploads.filename_length_invalid": "Failo pavadinimas turi būti nuo {min} iki {max} simbolių.",
"attachments_and_uploads.image_size_exceeds_limit": "Paveikslėlio dydis viršija {maxSize} baitų.",
"attachments_and_uploads.invalid_file_field_name": "Neteisingas failo lauko pavadinimas: {key}. Tikėtinas formatas: `files[N]`, kur N yra skaičius.",
@@ -372,7 +370,6 @@ const ERROR_I18N_LT_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Kad galėtum teikti ataskaitas, turi užbaigti paskyros nustatymą.",
"moderation_and_reports.unknown_report": "Nežinoma ataskaita.",
"moderation_and_reports.user_is_not_banned": "Šis vartotojas nėra banuotas.",
"names_and_normalization.channel_name_empty_after_normalization": "Kanalo pavadinimas negali būti tuščias po normalizavimo.",
"names_and_normalization.name_empty_after_normalization": "Pavadinimas negali būti tuščias po normalizavimo.",
"permissions.global_ip_banned": "Jūsų IP adresas {ipAddress} platformos administratorių buvo visam laikui užblokuotas Fluxer API. Jei manote, kad tai klaida, susisiekite su [email protected] dėl apeliacijos. Apeliacijoje nurodykite šį IP adresą.",
"permissions.global_ip_temporarily_banned": "Jūsų IP adresas {ipAddress} laikinai užblokuotas Fluxer API 24 valandoms dėl piktnaudžiavimo arba neįprastų prieigos modelių. Laikiniems API banams paprastai apeliacijų neteikiame. Pakeiskite IP adresą arba palaukite, kol banas baigsis, ir peržiūrėkite iš jūsų kliento ateinančius Fluxer API prieigos modelius.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_NL_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Bestandsindex {index} overschrijdt de maximaal toegestane index van {maxIndex}.",
"attachments_and_uploads.file_not_found": "Bestand niet gevonden.",
"attachments_and_uploads.file_size_too_large": "Bestandsgrootte is te groot. Maximale bestandsgrootte is {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Bestandsnaam kan niet leeg zijn na normalisatie.",
"attachments_and_uploads.filename_invalid_characters": "Bestandsnaam bevat ongeldige tekens.",
"attachments_and_uploads.filename_length_invalid": "Bestandsnaam moet tussen {min} en {max} tekens zijn.",
"attachments_and_uploads.image_size_exceeds_limit": "Afbeeldingsgrootte overschrijdt {maxSize} bytes.",
"attachments_and_uploads.invalid_file_field_name": "Ongeldige bestandsveldnaam: {key}. Verwacht formaat: `files[N]` waarbij N een getal is.",
@@ -372,7 +370,6 @@ const ERROR_I18N_NL_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Je moet je accountinstellingen voltooien voordat je meldingen kunt indienen.",
"moderation_and_reports.unknown_report": "Onbekend rapport.",
"moderation_and_reports.user_is_not_banned": "Deze gebruiker is niet verbannen.",
"names_and_normalization.channel_name_empty_after_normalization": "Kanaalnaam kan niet leeg zijn na normalisatie.",
"names_and_normalization.name_empty_after_normalization": "Naam kan niet leeg zijn na normalisatie.",
"permissions.global_ip_banned": "Je IP-adres {ipAddress} is door platformbeheerders permanent geblokkeerd voor de Fluxer API. Als je denkt dat dit een fout is, neem dan contact op met [email protected] om bezwaar te maken. Vermeld dit IP-adres in je bezwaar.",
"permissions.global_ip_temporarily_banned": "Je IP-adres {ipAddress} is tijdelijk 24 uur geblokkeerd voor de Fluxer API vanwege misbruikende of ongebruikelijke toegangspatronen. We bieden meestal geen bezwaarprocedure voor tijdelijke API-bans. Wijzig van IP-adres of wacht tot de ban verloopt, en controleer de toegangspatronen naar de Fluxer API vanuit je client.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_NO_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Filindeks {index} overskrider maksimalt tillatt indeks på {maxIndex}.",
"attachments_and_uploads.file_not_found": "Filen ble ikke funnet.",
"attachments_and_uploads.file_size_too_large": "Filstørrelsen er for stor. Maksimal filstørrelse er {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Filnavnet kan ikke være tomt etter normalisering.",
"attachments_and_uploads.filename_invalid_characters": "Filnavnet inneholder ugyldige tegn.",
"attachments_and_uploads.filename_length_invalid": "Filnavnet må være mellom {min} og {max} tegn.",
"attachments_and_uploads.image_size_exceeds_limit": "Bildestørrelsen overskrider {maxSize} byte.",
"attachments_and_uploads.invalid_file_field_name": "Ugyldig filfeltnavn: {key}. Forventet format: `files[N]` der N er et tall.",
@@ -372,7 +370,6 @@ const ERROR_I18N_NO_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Du må fullføre kontooppsettet ditt før du kan sende rapporter.",
"moderation_and_reports.unknown_report": "Ukjent rapport.",
"moderation_and_reports.user_is_not_banned": "Denne brukeren er ikke utestengt.",
"names_and_normalization.channel_name_empty_after_normalization": "Kanalnavnet kan ikke være tomt etter normalisering.",
"names_and_normalization.name_empty_after_normalization": "Navnet kan ikke være tomt etter normalisering.",
"permissions.global_ip_banned": "IP-adressen din {ipAddress} er permanent blokkert fra Fluxer API av plattformadministratorer. Hvis du mener dette er en feil, kontakt [email protected] for å klage. Ta med denne IP-adressen i klagen.",
"permissions.global_ip_temporarily_banned": "IP-adressen din {ipAddress} er midlertidig blokkert fra Fluxer API i 24 timer på grunn av misbrukende eller uvanlige tilgangsmønstre. Vi tilbyr vanligvis ikke klager for midlertidige API-bans. Bytt IP-adresse eller vent til bannen utløper, og gjennomgå tilgangsmønstrene til Fluxer API fra klienten din.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_PL_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Indeks pliku {index} przekracza maksymalną wartość indeksu {maxIndex}.",
"attachments_and_uploads.file_not_found": "Nie znaleziono pliku.",
"attachments_and_uploads.file_size_too_large": "Rozmiar pliku jest za duży. Maksymalny rozmiar pliku wynosi {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Nazwa pliku nie może być pusta po normalizacji.",
"attachments_and_uploads.filename_invalid_characters": "Nazwa pliku zawiera nieprawidłowe znaki.",
"attachments_and_uploads.filename_length_invalid": "Nazwa pliku musi mieć od {min} do {max} znaków.",
"attachments_and_uploads.image_size_exceeds_limit": "Rozmiar obrazu przekracza {maxSize} bajtów.",
"attachments_and_uploads.invalid_file_field_name": "Nieprawidłowa nazwa pola pliku: {key}. Oczekiwany format: `files[N]`, gdzie N to liczba.",
@@ -372,7 +370,6 @@ const ERROR_I18N_PL_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Dokończ konfigurację konta, zanim wyślesz zgłoszenia.",
"moderation_and_reports.unknown_report": "Nieznane zgłoszenie.",
"moderation_and_reports.user_is_not_banned": "Ten użytkownik nie ma bana.",
"names_and_normalization.channel_name_empty_after_normalization": "Nazwa kanału nie może być pusta po normalizacji.",
"names_and_normalization.name_empty_after_normalization": "Nazwa nie może być pusta po normalizacji.",
"permissions.global_ip_banned": "Twój adres IP {ipAddress} został trwale zablokowany w Fluxer API przez administratorów platformy. Jeśli uważasz, że to błąd, skontaktuj się z [email protected], aby się odwołać. Podaj ten adres IP w odwołaniu.",
"permissions.global_ip_temporarily_banned": "Twój adres IP {ipAddress} został tymczasowo zablokowany w Fluxer API na 24 godziny z powodu nadużyć lub nietypowych wzorców dostępu. Zazwyczaj nie oferujemy odwołań od tymczasowych banów API. Zmień adres IP albo poczekaj na wygaśnięcie bana i sprawdź wzorce dostępu do Fluxer API pochodzące z twojego klienta.",
@@ -63,8 +63,6 @@ const ERROR_I18N_PT_BR_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "O índice de arquivo {index} excede o índice máximo permitido de {maxIndex}.",
"attachments_and_uploads.file_not_found": "Arquivo não encontrado.",
"attachments_and_uploads.file_size_too_large": "O tamanho do arquivo é muito grande. O tamanho máximo do arquivo é {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "O nome do arquivo não pode estar vazio após a normalização.",
"attachments_and_uploads.filename_invalid_characters": "O nome do arquivo contém caracteres inválidos.",
"attachments_and_uploads.filename_length_invalid": "O nome do arquivo deve ter entre {min} e {max} caracteres.",
"attachments_and_uploads.image_size_exceeds_limit": "O tamanho da imagem excede {maxSize} bytes.",
"attachments_and_uploads.invalid_file_field_name": "Nome de campo de arquivo inválido: {key}. Formato esperado: `files[N]`, onde N é um número.",
@@ -372,7 +370,6 @@ const ERROR_I18N_PT_BR_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Você precisa terminar a configuração da sua conta antes de fazer denúncias.",
"moderation_and_reports.unknown_report": "Denúncia não encontrada.",
"moderation_and_reports.user_is_not_banned": "Este usuário não está banido.",
"names_and_normalization.channel_name_empty_after_normalization": "O nome do canal não pode estar vazio após a normalização.",
"names_and_normalization.name_empty_after_normalization": "O nome não pode ficar vazio após a normalização.",
"permissions.global_ip_banned": "Seu endereço IP {ipAddress} foi bloqueado permanentemente da API do Fluxer pelos administradores da plataforma. Se você acredita que isso é um erro, entre em contato com [email protected] para recorrer. Inclua este endereço IP no recurso.",
"permissions.global_ip_temporarily_banned": "Seu endereço IP {ipAddress} foi bloqueado temporariamente da API do Fluxer por 24 horas devido a padrões de acesso abusivos ou incomuns. Normalmente não oferecemos recursos para banimentos temporários da API. Troque de endereço IP ou aguarde o banimento expirar, e revise os padrões de acesso à API do Fluxer vindos do seu cliente.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_RO_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Indexul fișierului {index} depășește indexul maxim permis de {maxIndex}.",
"attachments_and_uploads.file_not_found": "Fișierul nu a fost găsit.",
"attachments_and_uploads.file_size_too_large": "Dimensiunea fișierului este prea mare. Dimensiunea maximă a fișierului este {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Numele fișierului nu poate fi gol după normalizare.",
"attachments_and_uploads.filename_invalid_characters": "Numele fișierului conține caractere nevalide.",
"attachments_and_uploads.filename_length_invalid": "Numele fișierului trebuie să aibă între {min} și {max} caractere.",
"attachments_and_uploads.image_size_exceeds_limit": "Dimensiunea imaginii depășește {maxSize} octeți.",
"attachments_and_uploads.invalid_file_field_name": "Nume câmp fișier nevalid: {key}. Format așteptat: `files[N]` unde N este un număr.",
@@ -372,7 +370,6 @@ const ERROR_I18N_RO_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Trebuie să-ți finalizezi configurarea contului înainte să poți trimite rapoarte.",
"moderation_and_reports.unknown_report": "Raportul este necunoscut.",
"moderation_and_reports.user_is_not_banned": "Acest utilizator nu e banat.",
"names_and_normalization.channel_name_empty_after_normalization": "Numele canalului nu poate fi gol după normalizare.",
"names_and_normalization.name_empty_after_normalization": "Numele nu poate fi gol după normalizare.",
"permissions.global_ip_banned": "Adresa ta IP {ipAddress} a fost blocată permanent din Fluxer API de administratorii platformei. Dacă crezi că este o greșeală, contactează [email protected] pentru apel. Include această adresă IP în apel.",
"permissions.global_ip_temporarily_banned": "Adresa ta IP {ipAddress} a fost blocată temporar din Fluxer API timp de 24 de ore din cauza unor tipare de acces abuzive sau neobișnuite. De obicei nu oferim apeluri pentru banurile temporare API. Schimbă adresa IP sau așteaptă expirarea banului și verifică tiparele de acces la Fluxer API provenite din clientul tău.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_RU_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Индекс файла {index} превышает максимальный индекс {maxIndex}.",
"attachments_and_uploads.file_not_found": "Файл не найден.",
"attachments_and_uploads.file_size_too_large": "Размер файла слишком большой. Максимальный размер файла: {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Имя файла не может быть пустым после нормализации.",
"attachments_and_uploads.filename_invalid_characters": "Имя файла содержит недопустимые символы.",
"attachments_and_uploads.filename_length_invalid": "Имя файла должно содержать от {min} до {max} символов.",
"attachments_and_uploads.image_size_exceeds_limit": "Размер изображения превышает {maxSize} байт.",
"attachments_and_uploads.invalid_file_field_name": "Недопустимое имя поля файла: {key}. Ожидаемый формат: `files[N]`, где N — число.",
@@ -372,7 +370,6 @@ const ERROR_I18N_RU_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Тебе нужно завершить настройку аккаунта, чтобы подавать жалобы.",
"moderation_and_reports.unknown_report": "Жалоба не найдена.",
"moderation_and_reports.user_is_not_banned": "Этот пользователь не забанен.",
"names_and_normalization.channel_name_empty_after_normalization": "Название канала не может быть пустым после обработки.",
"names_and_normalization.name_empty_after_normalization": "Имя не может быть пустым после обработки.",
"permissions.global_ip_banned": "Ваш IP-адрес {ipAddress} был навсегда заблокирован в Fluxer API администраторами платформы. Если вы считаете, что это ошибка, обратитесь на [email protected] для апелляции. Укажите этот IP-адрес в апелляции.",
"permissions.global_ip_temporarily_banned": "Ваш IP-адрес {ipAddress} временно заблокирован в Fluxer API на 24 часа из-за злоупотребляющих или необычных шаблонов доступа. Обычно мы не принимаем апелляции по временным API-банам. Смените IP-адрес или дождитесь окончания бана и проверьте шаблоны доступа к Fluxer API, поступающие от вашего клиента.",
@@ -63,8 +63,6 @@ const ERROR_I18N_SV_SE_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Filindex {index} överskrider det högsta tillåtna indexet på {maxIndex}.",
"attachments_and_uploads.file_not_found": "Filen hittades inte.",
"attachments_and_uploads.file_size_too_large": "Filstorleken är för stor. Max filstorlek är {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Filnamnet kan inte vara tomt efter normalisering.",
"attachments_and_uploads.filename_invalid_characters": "Filnamnet innehåller ogiltiga tecken.",
"attachments_and_uploads.filename_length_invalid": "Filnamnet måste vara mellan {min} och {max} tecken.",
"attachments_and_uploads.image_size_exceeds_limit": "Bildstorleken överskrider {maxSize} byte.",
"attachments_and_uploads.invalid_file_field_name": "Ogiltigt filfältnamn: {key}. Förväntat format: `files[N]` där N är en siffra.",
@@ -372,7 +370,6 @@ const ERROR_I18N_SV_SE_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Du måste slutföra dina kontoinställningar innan du kan skicka in rapporter.",
"moderation_and_reports.unknown_report": "Okänd rapport.",
"moderation_and_reports.user_is_not_banned": "Denna användare är inte bannad.",
"names_and_normalization.channel_name_empty_after_normalization": "Kanalnamnet kan inte vara tomt efter att ha normaliserats.",
"names_and_normalization.name_empty_after_normalization": "Namnet kan inte vara tomt efter att ha normaliserats.",
"permissions.global_ip_banned": "Din IP-adress {ipAddress} har blockerats permanent från Fluxer API av plattformsadministratörer. Om du tror att detta är ett misstag, kontakta [email protected] för att överklaga. Ta med den här IP-adressen i överklagan.",
"permissions.global_ip_temporarily_banned": "Din IP-adress {ipAddress} har blockerats tillfälligt från Fluxer API i 24 timmar på grund av missbrukande eller ovanliga åtkomstmönster. Vi erbjuder vanligtvis inte överklaganden för tillfälliga API-bans. Byt IP-adress eller vänta tills bannen löper ut och granska åtkomstmönstren till Fluxer API från din klient.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_TH_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "ดัชนีไฟล์ {index} เกินค่าสูงสุดที่อนุญาตคือ {maxIndex}",
"attachments_and_uploads.file_not_found": "ไม่พบไฟล์",
"attachments_and_uploads.file_size_too_large": "ขนาดไฟล์ใหญ่เกินไป ขนาดไฟล์สูงสุดคือ {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "ชื่อไฟล์ต้องไม่ว่างเปล่าหลังการปรับให้เป็นมาตรฐาน.",
"attachments_and_uploads.filename_invalid_characters": "ชื่อไฟล์มีอักขระที่ไม่ถูกต้อง.",
"attachments_and_uploads.filename_length_invalid": "ชื่อไฟล์ต้องมี {min} ถึง {max} ตัวอักษร.",
"attachments_and_uploads.image_size_exceeds_limit": "ขนาดรูปภาพเกิน {maxSize} ไบต์.",
"attachments_and_uploads.invalid_file_field_name": "ชื่อฟิลด์ไฟล์ไม่ถูกต้อง: {key} รูปแบบที่คาดไว้: `files[N]` โดยที่ N คือตัวเลข.",
@@ -372,7 +370,6 @@ const ERROR_I18N_TH_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "คุณต้องตั้งค่าบัญชีให้เสร็จสมบูรณ์ก่อนจึงจะสามารถส่งรายงานได้",
"moderation_and_reports.unknown_report": "ไม่พบรายงาน",
"moderation_and_reports.user_is_not_banned": "ผู้ใช้รายนี้ไม่ได้ถูกแบน",
"names_and_normalization.channel_name_empty_after_normalization": "ชื่อช่องต้องไม่ว่างเปล่าหลังจากการปรับให้เป็นมาตรฐาน",
"names_and_normalization.name_empty_after_normalization": "ชื่อต้องไม่ว่างเปล่าหลังจากการปรับให้เป็นมาตรฐาน",
"permissions.global_ip_banned": "ที่อยู่ IP ของคุณ {ipAddress} ถูกบล็อกจาก Fluxer API อย่างถาวรโดยผู้ดูแลแพลตฟอร์ม หากคุณเชื่อว่านี่เป็นข้อผิดพลาด โปรดติดต่อ [email protected] เพื่ออุทธรณ์ โปรดแนบที่อยู่ IP นี้ในการอุทธรณ์",
"permissions.global_ip_temporarily_banned": "ที่อยู่ IP ของคุณ {ipAddress} ถูกบล็อกจาก Fluxer API ชั่วคราวเป็นเวลา 24 ชั่วโมงเนื่องจากรูปแบบการเข้าถึงที่ผิดปกติหรือมีลักษณะละเมิด โดยปกติเราไม่รับอุทธรณ์สำหรับการแบน API ชั่วคราว โปรดเปลี่ยนที่อยู่ IP หรือรอให้การแบนหมดอายุ และตรวจสอบรูปแบบการเข้าถึง Fluxer API ที่มาจากไคลเอนต์ของคุณ",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_TR_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Dosya indeksi {index}, izin verilen maksimum indeks olan {maxIndex} değerini aşıyor.",
"attachments_and_uploads.file_not_found": "Dosya bulunamadı.",
"attachments_and_uploads.file_size_too_large": "Dosya boyutu çok büyük. İzin verilen maksimum dosya boyutu {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Normalizasyondan sonra dosya adını boş bırakamazsın.",
"attachments_and_uploads.filename_invalid_characters": "Dosya adında geçersiz karakterler var.",
"attachments_and_uploads.filename_length_invalid": "Dosya adı {min} ile {max} karakter arasında olmalı.",
"attachments_and_uploads.image_size_exceeds_limit": "Görsel boyutu {maxSize} baytı aşıyor.",
"attachments_and_uploads.invalid_file_field_name": "Geçersiz dosya alan adı: {key}. Beklenen biçim: `files[N]` (N, bir sayı).",
@@ -372,7 +370,6 @@ const ERROR_I18N_TR_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Rapor bildirmeden önce hesap kurulumunu tamamlamalısın.",
"moderation_and_reports.unknown_report": "Bilinmeyen rapor.",
"moderation_and_reports.user_is_not_banned": "Bu kullanıcı yasaklı değil.",
"names_and_normalization.channel_name_empty_after_normalization": "Kanal adı düzenlendikten sonra boş bırakılamaz.",
"names_and_normalization.name_empty_after_normalization": "Ad düzenlendikten sonra boş bırakılamaz.",
"permissions.global_ip_banned": "IP adresiniz {ipAddress}, platform yöneticileri tarafından Fluxer API'den kalıcı olarak engellendi. Bunun bir hata olduğunu düşünüyorsanız itiraz etmek için [email protected] ile iletişime geçin. İtirazınıza bu IP adresini ekleyin.",
"permissions.global_ip_temporarily_banned": "IP adresiniz {ipAddress}, kötüye kullanıma benzeyen veya olağan dışı erişim kalıpları nedeniyle Fluxer API'den 24 saatliğine geçici olarak engellendi. Geçici API banları için genellikle itiraz sunmuyoruz. IP adresinizi değiştirin veya banın süresinin dolmasını bekleyin ve istemcinizden Fluxer API'ye gelen erişim kalıplarını gözden geçirin.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_UK_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Індекс файлу {index} перевищує максимальний індекс {maxIndex}.",
"attachments_and_uploads.file_not_found": "Файл не знайдено.",
"attachments_and_uploads.file_size_too_large": "Розмір файлу завеликий. Максимальний розмір файлу: {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Ім'я файлу не може бути порожнім після нормалізації.",
"attachments_and_uploads.filename_invalid_characters": "Ім'я файлу містить неприпустимі символи.",
"attachments_and_uploads.filename_length_invalid": "Ім'я файлу має містити від {min} до {max} символів.",
"attachments_and_uploads.image_size_exceeds_limit": "Розмір зображення перевищує {maxSize} байтів.",
"attachments_and_uploads.invalid_file_field_name": "Некоректне ім'я поля файлу: {key}. Очікуваний формат: `files[N]`, де N — число.",
@@ -372,7 +370,6 @@ const ERROR_I18N_UK_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Тобі потрібно завершити налаштування акаунта, щоб надсилати звіти.",
"moderation_and_reports.unknown_report": "Звіт не знайдено.",
"moderation_and_reports.user_is_not_banned": "Цей користувач не забанений.",
"names_and_normalization.channel_name_empty_after_normalization": "Назва каналу не може бути порожньою після нормалізації.",
"names_and_normalization.name_empty_after_normalization": "Ім'я не може бути порожнім після нормалізації.",
"permissions.global_ip_banned": "Вашу IP-адресу {ipAddress} адміністратори платформи назавжди заблокували у Fluxer API. Якщо ви вважаєте, що це помилка, зверніться на [email protected] для апеляції. Вкажіть цю IP-адресу в апеляції.",
"permissions.global_ip_temporarily_banned": "Вашу IP-адресу {ipAddress} тимчасово заблоковано у Fluxer API на 24 години через зловживальні або незвичні шаблони доступу. Зазвичай ми не надаємо апеляції для тимчасових API-банів. Змініть IP-адресу або дочекайтеся завершення бану та перегляньте шаблони доступу до Fluxer API, що надходять від вашого клієнта.",
-3
View File
@@ -63,8 +63,6 @@ const ERROR_I18N_VI_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "Chỉ mục tệp {index} vượt quá chỉ mục tối đa cho phép là {maxIndex}.",
"attachments_and_uploads.file_not_found": "Không tìm thấy tệp.",
"attachments_and_uploads.file_size_too_large": "Kích thước tệp quá lớn. Tối đa {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Tên tệp không được để trống sau khi chuẩn hóa.",
"attachments_and_uploads.filename_invalid_characters": "Tên tệp có ký tự không hợp lệ.",
"attachments_and_uploads.filename_length_invalid": "Tên tệp phải có độ dài từ {min} đến {max} ký tự.",
"attachments_and_uploads.image_size_exceeds_limit": "Kích thước hình ảnh lớn hơn {maxSize} byte.",
"attachments_and_uploads.invalid_file_field_name": "Tên trường tệp không hợp lệ: {key}. Định dạng đúng: `files[N]` trong đó N là một số.",
@@ -372,7 +370,6 @@ const ERROR_I18N_VI_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Bạn cần hoàn tất thiết lập tài khoản trước khi có thể gửi báo cáo.",
"moderation_and_reports.unknown_report": "Báo cáo không xác định.",
"moderation_and_reports.user_is_not_banned": "Người dùng này không bị ban.",
"names_and_normalization.channel_name_empty_after_normalization": "Tên kênh không thể trống sau khi chuẩn hóa.",
"names_and_normalization.name_empty_after_normalization": "Tên không thể trống sau khi chuẩn hóa.",
"permissions.global_ip_banned": "Địa chỉ IP của bạn {ipAddress} đã bị quản trị viên nền tảng chặn vĩnh viễn khỏi Fluxer API. Nếu bạn cho rằng đây là nhầm lẫn, hãy liên hệ [email protected] để kháng nghị. Hãy kèm địa chỉ IP này trong kháng nghị.",
"permissions.global_ip_temporarily_banned": "Địa chỉ IP của bạn {ipAddress} đã bị chặn tạm thời khỏi Fluxer API trong 24 giờ do các mẫu truy cập lạm dụng hoặc bất thường. Chúng tôi thường không hỗ trợ kháng nghị cho các lệnh ban API tạm thời. Hãy đổi địa chỉ IP hoặc chờ lệnh ban hết hạn, và xem lại các mẫu truy cập Fluxer API đến từ client của bạn.",
@@ -63,8 +63,6 @@ const ERROR_I18N_ZH_CN_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "文件索引 {index} 超过了允许的最大索引 {maxIndex}。",
"attachments_and_uploads.file_not_found": "文件未找到。",
"attachments_and_uploads.file_size_too_large": "文件大小过大。最大文件大小为 {maxSize}。",
"attachments_and_uploads.filename_empty_after_normalization": "规范化后文件名不可为空。",
"attachments_and_uploads.filename_invalid_characters": "文件名包含无效字符。",
"attachments_and_uploads.filename_length_invalid": "文件名必须在 {min} 到 {max} 个字符之间。",
"attachments_and_uploads.image_size_exceeds_limit": "图片大小超出 {maxSize} 字节。",
"attachments_and_uploads.invalid_file_field_name": "无效的文件字段名称:{key}。预期格式:`files[N]`,其中 N 为数字。",
@@ -372,7 +370,6 @@ const ERROR_I18N_ZH_CN_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "你需要完成账号设置才能提交举报。",
"moderation_and_reports.unknown_report": "未知举报。",
"moderation_and_reports.user_is_not_banned": "此用户未被封禁。",
"names_and_normalization.channel_name_empty_after_normalization": "规范化后频道名称不能为空。",
"names_and_normalization.name_empty_after_normalization": "规范化后名称不能为空。",
"permissions.global_ip_banned": "你的 IP 地址 {ipAddress} 已被平台管理员永久禁止访问 Fluxer API。如果你认为这是误判,请联系 [email protected] 申诉。请在申诉中附上此 IP 地址。",
"permissions.global_ip_temporarily_banned": "你的 IP 地址 {ipAddress} 因存在滥用或异常访问模式,已被临时禁止访问 Fluxer API 24 小时。临时 API ban 通常不提供申诉。请更换 IP 地址或等待 ban 过期,并检查你的客户端对 Fluxer API 的访问模式。",
@@ -63,8 +63,6 @@ const ERROR_I18N_ZH_TW_MESSAGES = defineErrorI18nLocaleMessages({
"attachments_and_uploads.file_index_exceeds_maximum": "檔案索引 {index} 超過允許的最大索引 {maxIndex}。",
"attachments_and_uploads.file_not_found": "找不到檔案。",
"attachments_and_uploads.file_size_too_large": "檔案大小過大。最大檔案大小為 {maxSize}。",
"attachments_and_uploads.filename_empty_after_normalization": "正規化後檔案名稱不能為空。",
"attachments_and_uploads.filename_invalid_characters": "檔案名稱包含無效字元。",
"attachments_and_uploads.filename_length_invalid": "檔案名稱必須介於 {min} 到 {max} 個字元之間。",
"attachments_and_uploads.image_size_exceeds_limit": "圖片大小超過 {maxSize} 位元組。",
"attachments_and_uploads.invalid_file_field_name": "無效的檔案欄位名稱:{key}。預期格式為 `files[N]`,其中 N 為數字。",
@@ -372,7 +370,6 @@ const ERROR_I18N_ZH_TW_MESSAGES = defineErrorI18nLocaleMessages({
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "你必須完成帳號設定才能送出檢舉。",
"moderation_and_reports.unknown_report": "不明的檢舉。",
"moderation_and_reports.user_is_not_banned": "此使用者未被封鎖。",
"names_and_normalization.channel_name_empty_after_normalization": "正規化後頻道名稱不能為空。",
"names_and_normalization.name_empty_after_normalization": "正規化後名稱不可為空。",
"permissions.global_ip_banned": "你的 IP 位址 {ipAddress} 已被平台管理員永久禁止存取 Fluxer API。如果你認為這是誤判,請聯絡 [email protected] 申訴。請在申訴中附上此 IP 位址。",
"permissions.global_ip_temporarily_banned": "你的 IP 位址 {ipAddress} 因濫用或異常存取模式,已被暫時禁止存取 Fluxer API 24 小時。暫時 API ban 通常不提供申訴。請更換 IP 位址或等待 ban 到期,並檢查你的用戶端對 Fluxer API 的存取模式。",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "فهرس الملف {index} يتجاوز الحد الأقصى المسموح به وهو {maxIndex}.",
"attachments_and_uploads.file_not_found": "لم يتم العثور على الملف.",
"attachments_and_uploads.file_size_too_large": "حجم الملف كبير جدًا. الحد الأقصى {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "لا يمكن أن يكون اسم الملف فارغًا بعد التسوية.",
"attachments_and_uploads.filename_invalid_characters": "اسم الملف يحتوي على أحرف غير صالحة.",
"attachments_and_uploads.filename_length_invalid": "يجب أن يكون اسم الملف بين {min} و {max} حرفًا.",
"attachments_and_uploads.image_size_exceeds_limit": "حجم الصورة يتجاوز {maxSize} بايت.",
"attachments_and_uploads.invalid_file_field_name": "اسم حقل ملف غير صالح: {key}. التنسيق المتوقع: `files[N]` حيث N هو رقم.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "تحتاج إلى إكمال إعداد حسابك قبل إرسال التقارير.",
"moderation_and_reports.unknown_report": "تقرير غير معروف.",
"moderation_and_reports.user_is_not_banned": "المستخدم غير محظور.",
"names_and_normalization.channel_name_empty_after_normalization": "لا يمكن أن يكون اسم القناة فارغًا بعد التسوية.",
"names_and_normalization.name_empty_after_normalization": "لا يمكن أن يكون الاسم فارغًا بعد التسوية.",
"permissions.global_ip_banned": "تم حظر عنوان IP الخاص بك {ipAddress} نهائيا من Fluxer API بواسطة مسؤولي المنصة. إذا كنت تعتقد أن هذا خطأ، فتواصل مع [email protected] للاستئناف. اذكر عنوان IP هذا في الاستئناف.",
"permissions.global_ip_temporarily_banned": "تم حظر عنوان IP الخاص بك {ipAddress} مؤقتا من Fluxer API لمدة 24 ساعة بسبب أنماط وصول مسيئة أو غير معتادة. لا نوفر عادة استئنافات لحظر API المؤقت. غيّر عنوان IP أو انتظر انتهاء الحظر، وراجع أنماط الوصول إلى Fluxer API القادمة من عميلك.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Файловият индекс {index} надвишава максимално допустимия индекс от {maxIndex}.",
"attachments_and_uploads.file_not_found": "Файлът не е намерен.",
"attachments_and_uploads.file_size_too_large": "Размерът на файла е твърде голям. Максималният размер е {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Името на файла не може да е празно след нормализация.",
"attachments_and_uploads.filename_invalid_characters": "Името на файла съдържа невалидни символи.",
"attachments_and_uploads.filename_length_invalid": "Името на файла трябва да е между {min} и {max} символа.",
"attachments_and_uploads.image_size_exceeds_limit": "Размерът на изображението е над {maxSize} байта.",
"attachments_and_uploads.invalid_file_field_name": "Невалидно име на поле за файл: {key}. Очакван формат: `files[N]`, където N е число.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Трябва да завършиш акаунта си, преди да можеш да подаваш доклади.",
"moderation_and_reports.unknown_report": "Неизвестен доклад.",
"moderation_and_reports.user_is_not_banned": "Този потребител не е баннат.",
"names_and_normalization.channel_name_empty_after_normalization": "Името на канала не може да бъде празно след нормализиране.",
"names_and_normalization.name_empty_after_normalization": "Името не може да бъде празно след нормализиране.",
"permissions.global_ip_banned": "Вашият IP адрес {ipAddress} е блокиран за постоянно от Fluxer API от администраторите на платформата. Ако смятате, че това е грешка, свържете се със [email protected], за да обжалвате. Включете този IP адрес в обжалването.",
"permissions.global_ip_temporarily_banned": "Вашият IP адрес {ipAddress} е временно блокиран от Fluxer API за 24 часа поради злоупотребяващи или необичайни модели на достъп. Обикновено не предлагаме обжалвания за временни API блокировки. Сменете IP адреса или изчакайте блокировката да изтече и прегледайте моделите на достъп до Fluxer API от вашия клиент.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Index souboru {index} překračuje maximální povolený index {maxIndex}.",
"attachments_and_uploads.file_not_found": "Soubor nebyl nalezen.",
"attachments_and_uploads.file_size_too_large": "Velikost souboru je příliš velká. Maximální velikost souboru je {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Název souboru nemůže být po normalizaci prázdný.",
"attachments_and_uploads.filename_invalid_characters": "Název souboru obsahuje neplatné znaky.",
"attachments_and_uploads.filename_length_invalid": "Název souboru musí být dlouhý {min} až {max} znaků.",
"attachments_and_uploads.image_size_exceeds_limit": "Velikost obrázku přesahuje {maxSize} bajtů.",
"attachments_and_uploads.invalid_file_field_name": "Neplatný název pole souboru: {key}. Očekávaný formát je `files[N]`, kde N je číslo.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Než budeš moci odesílat hlášení, musíš nejdřív dokončit nastavení svého účtu.",
"moderation_and_reports.unknown_report": "Neznámé hlášení.",
"moderation_and_reports.user_is_not_banned": "Tento uživatel není zabanován.",
"names_and_normalization.channel_name_empty_after_normalization": "Název kanálu nemůže být po normalizaci prázdný.",
"names_and_normalization.name_empty_after_normalization": "Název nemůže být po normalizaci prázdný.",
"permissions.global_ip_banned": "Vaše IP adresa {ipAddress} byla správci platformy trvale zablokována pro Fluxer API. Pokud si myslíte, že jde o chybu, kontaktujte [email protected] a požádejte o přezkum. Uveďte tuto IP adresu v žádosti.",
"permissions.global_ip_temporarily_banned": "Vaše IP adresa {ipAddress} byla dočasně zablokována pro Fluxer API na 24 hodin kvůli zneužívajícím nebo neobvyklým vzorcům přístupu. U dočasných zákazů API obvykle nenabízíme odvolání. Změňte IP adresu nebo počkejte na vypršení zákazu a zkontrolujte vzorce přístupu k Fluxer API z vašeho klienta.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Filindeks {index} overskrider det maksimalt tilladte indeks på {maxIndex}.",
"attachments_and_uploads.file_not_found": "Fil ikke fundet.",
"attachments_and_uploads.file_size_too_large": "Filstørrelsen er for stor. Maksimal filstørrelse er {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Filnavnet kan ikke være tomt efter normalisering.",
"attachments_and_uploads.filename_invalid_characters": "Filnavnet indeholder ugyldige tegn.",
"attachments_and_uploads.filename_length_invalid": "Filnavnet skal være mellem {min} og {max} tegn.",
"attachments_and_uploads.image_size_exceeds_limit": "Billedstørrelsen overskrider {maxSize} bytes.",
"attachments_and_uploads.invalid_file_field_name": "Ugyldigt filfeltnavn: {key}. Forventet format: `files[N]`, hvor N er et tal.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Du skal fuldføre din kontoopsætning, før du kan indsende rapporter.",
"moderation_and_reports.unknown_report": "Ukendt rapport.",
"moderation_and_reports.user_is_not_banned": "Denne bruger er ikke bandlyst.",
"names_and_normalization.channel_name_empty_after_normalization": "Kanalnavnet må ikke være tomt efter normalisering.",
"names_and_normalization.name_empty_after_normalization": "Navnet må ikke være tomt efter normalisering.",
"permissions.global_ip_banned": "Din IP-adresse {ipAddress} er permanent blokeret fra Fluxer API af platformadministratorer. Hvis du mener, at dette er en fejl, kan du kontakte [email protected] for at klage. Medtag denne IP-adresse i klagen.",
"permissions.global_ip_temporarily_banned": "Din IP-adresse {ipAddress} er midlertidigt blokeret fra Fluxer API i 24 timer på grund af misbrugende eller usædvanlige adgangsmønstre. Vi tilbyder normalt ikke klager for midlertidige API-bans. Skift IP-adresse eller vent på, at blokeringen udløber, og gennemgå adgangsmønstrene til Fluxer API fra din klient.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Der Dateiindex {index} überschreitet den maximal zulässigen Index von {maxIndex}.",
"attachments_and_uploads.file_not_found": "Datei nicht gefunden.",
"attachments_and_uploads.file_size_too_large": "Die Dateigröße ist zu groß. Die maximale Dateigröße beträgt {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Der Dateiname darf nach der Normalisierung nicht leer sein.",
"attachments_and_uploads.filename_invalid_characters": "Der Dateiname enthält ungültige Zeichen.",
"attachments_and_uploads.filename_length_invalid": "Der Dateiname muss zwischen {min} und {max} Zeichen lang sein.",
"attachments_and_uploads.image_size_exceeds_limit": "Die Bildgröße überschreitet {maxSize} Bytes.",
"attachments_and_uploads.invalid_file_field_name": "Ungültiger Dateifeldname: {key}. Erwartetes Format: `files[N]`, wobei N eine Zahl ist.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Du musst deinen Account einrichten, bevor du Meldungen absenden kannst.",
"moderation_and_reports.unknown_report": "Unbekannte Meldung.",
"moderation_and_reports.user_is_not_banned": "Dieser Benutzer ist nicht gebannt.",
"names_and_normalization.channel_name_empty_after_normalization": "Der Kanalname kann nach der Normalisierung nicht leer sein.",
"names_and_normalization.name_empty_after_normalization": "Der Name darf nach der Normalisierung nicht leer bleiben.",
"permissions.global_ip_banned": "Deine IP-Adresse {ipAddress} wurde von Plattformadministratoren dauerhaft von der Fluxer API gesperrt. Wenn du glaubst, dass dies ein Fehler ist, kontaktiere [email protected], um Einspruch einzulegen. Gib diese IP-Adresse in deinem Einspruch an.",
"permissions.global_ip_temporarily_banned": "Deine IP-Adresse {ipAddress} wurde wegen missbräuchlicher oder ungewöhnlicher Zugriffsmuster vorübergehend für 24 Stunden von der Fluxer API gesperrt. Für temporäre API-Sperren bieten wir normalerweise keine Einsprüche an. Wechsle die IP-Adresse oder warte, bis die Sperre abläuft, und überprüfe die Zugriffsmuster deines Clients auf die Fluxer API.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Το ευρετήριο αρχείου {index} ξεπερνά το μέγιστο επιτρεπόμενο {maxIndex}.",
"attachments_and_uploads.file_not_found": "Το αρχείο δεν βρέθηκε.",
"attachments_and_uploads.file_size_too_large": "Το μέγεθος του αρχείου είναι πολύ μεγάλο. Το μέγιστο μέγεθος αρχείου είναι {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Το όνομα αρχείου δεν μπορεί να είναι άδειο μετά την κανονικοποίηση.",
"attachments_and_uploads.filename_invalid_characters": "Το όνομα αρχείου περιέχει μη έγκυρους χαρακτήρες.",
"attachments_and_uploads.filename_length_invalid": "Το όνομα αρχείου πρέπει να έχει μεταξύ {min} και {max} χαρακτήρες.",
"attachments_and_uploads.image_size_exceeds_limit": "Το μέγεθος της εικόνας ξεπερνά τα {maxSize} bytes.",
"attachments_and_uploads.invalid_file_field_name": "Μη έγκυρο όνομα πεδίου αρχείου: {key}. Η αναμενόμενη μορφή είναι: `files[N]` όπου N είναι ένας αριθμός.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Πρέπει να ολοκληρώσεις τη ρύθμιση του λογαριασμού σου πριν υποβάλεις αναφορές.",
"moderation_and_reports.unknown_report": "Δεν βρέθηκε η αναφορά.",
"moderation_and_reports.user_is_not_banned": "Αυτός ο χρήστης δεν έχει αποκλειστεί.",
"names_and_normalization.channel_name_empty_after_normalization": "Το όνομα του καναλιού δεν μπορεί να είναι κενό μετά την κανονικοποίηση.",
"names_and_normalization.name_empty_after_normalization": "Το όνομα δεν μπορεί να είναι κενό μετά την κανονικοποίηση.",
"permissions.global_ip_banned": "Η διεύθυνση IP σου {ipAddress} έχει αποκλειστεί μόνιμα από το Fluxer API από τους διαχειριστές της πλατφόρμας. Αν πιστεύεις ότι πρόκειται για λάθος, επικοινώνησε με [email protected] για ένσταση. Συμπερίλαβε αυτήν τη διεύθυνση IP στην ένστασή σου.",
"permissions.global_ip_temporarily_banned": "Η διεύθυνση IP σου {ipAddress} έχει αποκλειστεί προσωρινά από το Fluxer API για 24 ώρες λόγω καταχρηστικών ή ασυνήθιστων μοτίβων πρόσβασης. Συνήθως δεν παρέχουμε ενστάσεις για προσωρινούς αποκλεισμούς API. Άλλαξε διεύθυνση IP ή περίμενε να λήξει ο αποκλεισμός και έλεγξε τα μοτίβα πρόσβασης στο Fluxer API από τον client σου.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "File index {index} exceeds the maximum allowed index of {maxIndex}.",
"attachments_and_uploads.file_not_found": "File not found.",
"attachments_and_uploads.file_size_too_large": "File size is too large. Maximum file size is {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Filename can't be empty after normalization.",
"attachments_and_uploads.filename_invalid_characters": "Filename contains invalid characters.",
"attachments_and_uploads.filename_length_invalid": "Filename must be between {min} and {max} characters.",
"attachments_and_uploads.image_size_exceeds_limit": "Image size exceeds {maxSize} bytes.",
"attachments_and_uploads.invalid_file_field_name": "Invalid file field name: {key}. Expected format: `files[N]` where N is a number.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "You need to complete your account setup before you can submit reports.",
"moderation_and_reports.unknown_report": "Unknown report.",
"moderation_and_reports.user_is_not_banned": "This user isn't banned.",
"names_and_normalization.channel_name_empty_after_normalization": "Channel name can't be empty after normalisation.",
"names_and_normalization.name_empty_after_normalization": "Name can't be empty after normalisation.",
"permissions.global_ip_banned": "Your IP address {ipAddress} has been permanently blocked from the Fluxer API by platform administrators. If you believe this is a mistake, contact [email protected] to appeal. Include this IP address in your appeal.",
"permissions.global_ip_temporarily_banned": "Your IP address {ipAddress} has been temporarily blocked from the Fluxer API for 24 hours because of abusive or unusual access patterns. We usually do not provide appeals for temporary API bans. Change IP addresses or wait for the ban to expire, and review the Fluxer API access patterns coming from your client.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "El índice de archivo {index} excede el índice máximo permitido de {maxIndex}.",
"attachments_and_uploads.file_not_found": "Archivo no encontrado.",
"attachments_and_uploads.file_size_too_large": "El tamaño del archivo es demasiado grande. El tamaño máximo del archivo es {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "El nombre de archivo no puede estar vacío después de la normalización.",
"attachments_and_uploads.filename_invalid_characters": "El nombre de archivo contiene caracteres no válidos.",
"attachments_and_uploads.filename_length_invalid": "El nombre de archivo debe tener entre {min} y {max} caracteres.",
"attachments_and_uploads.image_size_exceeds_limit": "El tamaño de la imagen excede los {maxSize} bytes.",
"attachments_and_uploads.invalid_file_field_name": "Nombre de campo de archivo no válido: {key}. Formato esperado: `files[N]` donde N es un número.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Debes completar la configuración de tu cuenta antes de poder enviar denuncias.",
"moderation_and_reports.unknown_report": "Denuncia desconocida.",
"moderation_and_reports.user_is_not_banned": "Este usuario no ha sido baneado.",
"names_and_normalization.channel_name_empty_after_normalization": "El nombre del canal no puede quedar vacío después de la normalización.",
"names_and_normalization.name_empty_after_normalization": "El nombre no puede quedar vacío después de la normalización.",
"permissions.global_ip_banned": "Tu dirección IP {ipAddress} fue bloqueada permanentemente de la API de Fluxer por administradores de la plataforma. Si crees que es un error, escribe a [email protected] para apelar. Incluye esta dirección IP en tu apelación.",
"permissions.global_ip_temporarily_banned": "Tu dirección IP {ipAddress} fue bloqueada temporalmente de la API de Fluxer durante 24 horas por patrones de acceso abusivos o inusuales. Por lo general no ofrecemos apelaciones para bloqueos temporales de la API. Cambia de dirección IP o espera a que termine el bloqueo, y revisa los patrones de acceso a la API de Fluxer que provienen de tu cliente.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "El índice de archivo {index} supera el máximo permitido de {maxIndex}.",
"attachments_and_uploads.file_not_found": "Archivo no encontrado.",
"attachments_and_uploads.file_size_too_large": "El archivo es demasiado grande. El tamaño máximo de archivo es {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "El nombre del archivo no puede estar vacío después de la normalización.",
"attachments_and_uploads.filename_invalid_characters": "El nombre del archivo contiene caracteres no válidos.",
"attachments_and_uploads.filename_length_invalid": "El nombre del archivo debe tener entre {min} y {max} caracteres.",
"attachments_and_uploads.image_size_exceeds_limit": "El tamaño de la imagen supera los {maxSize} bytes.",
"attachments_and_uploads.invalid_file_field_name": "Nombre de campo de archivo no válido: {key}. Formato esperado: `files[N]` donde N es un número.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Necesitas completar la configuración de tu cuenta antes de poder enviar informes.",
"moderation_and_reports.unknown_report": "Informe desconocido.",
"moderation_and_reports.user_is_not_banned": "Este usuario no está baneado.",
"names_and_normalization.channel_name_empty_after_normalization": "El nombre del canal no puede quedar vacío tras la normalización.",
"names_and_normalization.name_empty_after_normalization": "El nombre no puede quedar vacío tras la normalización.",
"permissions.global_ip_banned": "Tu dirección IP {ipAddress} ha sido bloqueada permanentemente de la API de Fluxer por administradores de la plataforma. Si crees que es un error, contacta con [email protected] para apelar. Incluye esta dirección IP en la apelación.",
"permissions.global_ip_temporarily_banned": "Tu dirección IP {ipAddress} ha sido bloqueada temporalmente de la API de Fluxer durante 24 horas por patrones de acceso abusivos o inusuales. Normalmente no ofrecemos apelaciones para baneos temporales de la API. Cambia de dirección IP o espera a que expire el bloqueo, y revisa los patrones de acceso a la API de Fluxer que provienen de tu cliente.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Tiedoston indeksi {index} ylittää suurimman sallitun indeksin {maxIndex}.",
"attachments_and_uploads.file_not_found": "Tiedostoa ei löytynyt.",
"attachments_and_uploads.file_size_too_large": "Tiedoston koko on liian suuri. Suurin sallittu koko on {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Tiedostonimi ei saa olla tyhjä normalisoinnin jälkeen.",
"attachments_and_uploads.filename_invalid_characters": "Tiedostonimi sisältää virheellisiä merkkejä.",
"attachments_and_uploads.filename_length_invalid": "Tiedostonimen on oltava {min}–{max} merkkiä pitkä.",
"attachments_and_uploads.image_size_exceeds_limit": "Kuvan koko on yli {maxSize} tavua.",
"attachments_and_uploads.invalid_file_field_name": "Virheellinen tiedostokentän nimi: {key}. Odotettu muoto: `files[N]`, jossa N on numero.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Viimeistele tilisi asetukset, ennen kuin voit tehdä ilmiantoja.",
"moderation_and_reports.unknown_report": "Tuntematon ilmianto.",
"moderation_and_reports.user_is_not_banned": "Tätä käyttäjää ei ole porttikielletty.",
"names_and_normalization.channel_name_empty_after_normalization": "Normalisoinnin jälkeen kanavan nimi ei voi olla tyhjä.",
"names_and_normalization.name_empty_after_normalization": "Normalisoinnin jälkeen nimi ei voi olla tyhjä.",
"permissions.global_ip_banned": "IP-osoitteesi {ipAddress} on estetty pysyvästi Fluxer APIsta alustan ylläpitäjien toimesta. Jos uskot tämän olevan virhe, ota yhteyttä [email protected] tehdäksesi valituksen. Liitä tämä IP-osoite valitukseen.",
"permissions.global_ip_temporarily_banned": "IP-osoitteesi {ipAddress} on estetty väliaikaisesti Fluxer APIsta 24 tunniksi väärinkäyttöä muistuttavien tai epätavallisten käyttötapojen vuoksi. Emme yleensä tarjoa valituksia väliaikaisista API-estoista. Vaihda IP-osoitetta tai odota eston päättymistä ja tarkista asiakkaastasi tulevat Fluxer API -käyttötavat.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "L'index de fichier {index} dépasse l'index maximal autorisé de {maxIndex}.",
"attachments_and_uploads.file_not_found": "Fichier introuvable.",
"attachments_and_uploads.file_size_too_large": "La taille du fichier est trop grande. La taille maximale du fichier est de {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Le nom de fichier ne peut pas être vide après normalisation.",
"attachments_and_uploads.filename_invalid_characters": "Le nom de fichier contient des caractères non valides.",
"attachments_and_uploads.filename_length_invalid": "Le nom de fichier doit contenir entre {min} et {max} caractères.",
"attachments_and_uploads.image_size_exceeds_limit": "La taille de l'image dépasse {maxSize} octets.",
"attachments_and_uploads.invalid_file_field_name": "Nom de champ de fichier non valide : {key}. Format attendu : `files[N]` où N est un nombre.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Tu dois finaliser la configuration de ton compte avant de pouvoir soumettre des signalements.",
"moderation_and_reports.unknown_report": "Signalement inconnu.",
"moderation_and_reports.user_is_not_banned": "Cet utilisateur n'est pas banni.",
"names_and_normalization.channel_name_empty_after_normalization": "Le nom du salon ne peut pas être vide une fois normalisé.",
"names_and_normalization.name_empty_after_normalization": "Le nom ne peut pas être vide une fois normalisé.",
"permissions.global_ip_banned": "Ton adresse IP {ipAddress} a été définitivement bloquée de l'API Fluxer par les administrateurs de la plateforme. Si tu penses qu'il s'agit d'une erreur, contacte [email protected] pour faire appel. Indique cette adresse IP dans ton appel.",
"permissions.global_ip_temporarily_banned": "Ton adresse IP {ipAddress} a été temporairement bloquée de l'API Fluxer pendant 24 heures en raison de schémas d'accès abusifs ou inhabituels. Nous ne proposons généralement pas d'appel pour les bannissements temporaires de l'API. Change d'adresse IP ou attends l'expiration du bannissement, et vérifie les schémas d'accès à l'API Fluxer provenant de ton client.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "אינדקס הקובץ {index} חורג מהאינדקס המקסימלי המותר {maxIndex}.",
"attachments_and_uploads.file_not_found": "קובץ לא נמצא.",
"attachments_and_uploads.file_size_too_large": "גודל הקובץ גדול מדי. הגודל המקסימלי הוא {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "שם הקובץ אינו יכול להיות ריק לאחר נורמליזציה.",
"attachments_and_uploads.filename_invalid_characters": "שם הקובץ מכיל תווים אסורים.",
"attachments_and_uploads.filename_length_invalid": "שם הקובץ חייב להיות באורך של {min} עד {max} תווים.",
"attachments_and_uploads.image_size_exceeds_limit": "גודל התמונה עולה על {maxSize} בתים.",
"attachments_and_uploads.invalid_file_field_name": "שם שדה קובץ לא חוקי: {key}. הפורמט הצפוי: `files[N]` כאשר N הוא מספר.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "עליך להשלים את הגדרות החשבון שלך לפני הגשת דיווחים.",
"moderation_and_reports.unknown_report": "דיווח שגוי.",
"moderation_and_reports.user_is_not_banned": "משתמש זה לא חסום.",
"names_and_normalization.channel_name_empty_after_normalization": "שם הערוץ לא יכול להיות ריק לאחר נורמליזציה.",
"names_and_normalization.name_empty_after_normalization": "השם לא יכול להיות ריק לאחר נורמליזציה.",
"permissions.global_ip_banned": "כתובת ה-IP שלך {ipAddress} נחסמה לצמיתות מ-Fluxer API על ידי מנהלי הפלטפורמה. אם לדעתך זו טעות, אפשר לפנות אל [email protected] כדי לערער. יש לכלול את כתובת ה-IP הזו בערעור.",
"permissions.global_ip_temporarily_banned": "כתובת ה-IP שלך {ipAddress} נחסמה זמנית מ-Fluxer API למשך 24 שעות בגלל דפוסי גישה פוגעניים או חריגים. בדרך כלל איננו מאפשרים ערעורים על חסימות API זמניות. החלף כתובת IP או המתן לסיום החסימה, ובדוק את דפוסי הגישה ל-Fluxer API שמגיעים מהלקוח שלך.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "फाइल इंडेक्स {index} अधिकतम अनुमत इंडेक्स {maxIndex} से अधिक है।",
"attachments_and_uploads.file_not_found": "फाइल नहीं मिली।",
"attachments_and_uploads.file_size_too_large": "फाइल का साइज बहुत ज़्यादा है। अधिकतम फाइल साइज {maxSize} है।",
"attachments_and_uploads.filename_empty_after_normalization": "नॉर्मलाइज़ेशन के बाद फाइलनेम खाली नहीं हो सकता।",
"attachments_and_uploads.filename_invalid_characters": "फाइलनेम में अमान्य अक्षर हैं।",
"attachments_and_uploads.filename_length_invalid": "फाइलनेम {min} और {max} अक्षर के बीच में होना चाहिए।",
"attachments_and_uploads.image_size_exceeds_limit": "इमेज का साइज {maxSize} बाइट्स से अधिक है।",
"attachments_and_uploads.invalid_file_field_name": "अमान्य फाइल फील्ड नेम: {key}। सही फॉर्मेट: `files[N]` जहाँ N एक नंबर है।",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "रिपोर्ट सबमिट करने से पहले आपको अपना अकाउंट सेटअप पूरा करना होगा।",
"moderation_and_reports.unknown_report": "अज्ञात रिपोर्ट।",
"moderation_and_reports.user_is_not_banned": "यह यूज़र बैन नहीं है।",
"names_and_normalization.channel_name_empty_after_normalization": "नॉर्मलाइज़ेशन के बाद चैनल का नाम खाली नहीं हो सकता।",
"names_and_normalization.name_empty_after_normalization": "नॉर्मलाइज़ेशन के बाद नाम खाली नहीं हो सकता।",
"permissions.global_ip_banned": "आपका IP पता {ipAddress} प्लेटफ़ॉर्म एडमिनों द्वारा Fluxer API से स्थायी रूप से ब्लॉक कर दिया गया है। अगर आपको लगता है कि यह गलती है, तो अपील के लिए [email protected] से संपर्क करें। अपनी अपील में यह IP पता शामिल करें।",
"permissions.global_ip_temporarily_banned": "आपका IP पता {ipAddress} दुर्व्यवहारपूर्ण या असामान्य access patterns के कारण Fluxer API से 24 घंटे के लिए अस्थायी रूप से ब्लॉक कर दिया गया है। अस्थायी API bans के लिए हम आम तौर पर appeals नहीं देते। IP पता बदलें या ban समाप्त होने तक प्रतीक्षा करें, और अपने client से Fluxer API पर आने वाले access patterns की समीक्षा करें।",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Indeks datoteke {index} premašuje maksimalno dopušteni indeks od {maxIndex}.",
"attachments_and_uploads.file_not_found": "Datoteka nije pronađena.",
"attachments_and_uploads.file_size_too_large": "Veličina datoteke je prevelika. Maksimalna veličina datoteke je {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Naziv datoteke ne može biti prazan nakon normalizacije.",
"attachments_and_uploads.filename_invalid_characters": "Naziv datoteke sadrži nevažeće znakove.",
"attachments_and_uploads.filename_length_invalid": "Naziv datoteke mora imati između {min} i {max} znakova.",
"attachments_and_uploads.image_size_exceeds_limit": "Veličina slike premašuje {maxSize} bajtova.",
"attachments_and_uploads.invalid_file_field_name": "Nevažeći naziv polja datoteke: {key}. Očekivani format: `files[N]` gdje je N broj.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Moraš dovršiti postavljanje računa prije nego što možeš podnositi izvješća.",
"moderation_and_reports.unknown_report": "Nepoznato izvješće.",
"moderation_and_reports.user_is_not_banned": "Ovaj korisnik nije banan.",
"names_and_normalization.channel_name_empty_after_normalization": "Naziv kanala ne može biti prazan poslije normalizacije.",
"names_and_normalization.name_empty_after_normalization": "Ime ne može biti prazno poslije normalizacije.",
"permissions.global_ip_banned": "Tvoja IP adresa {ipAddress} trajno je blokirana za Fluxer API od strane administratora platforme. Ako smatraš da je to pogreška, kontaktiraj [email protected] radi žalbe. Uključi ovu IP adresu u žalbu.",
"permissions.global_ip_temporarily_banned": "Tvoja IP adresa {ipAddress} privremeno je blokirana za Fluxer API na 24 sata zbog zloupotrebljavajućih ili neuobičajenih obrazaca pristupa. Obično ne nudimo žalbe za privremene API banove. Promijeni IP adresu ili pričekaj da ban istekne i pregledaj obrasce pristupa Fluxer API-ju koji dolaze iz tvog klijenta.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "A(z) {index}. fájl indexe meghaladja a maximálisan engedélyezett {maxIndex} indexet.",
"attachments_and_uploads.file_not_found": "A fájl nem található.",
"attachments_and_uploads.file_size_too_large": "A fájl mérete túl nagy. A megengedett maximális fájlméret {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "A fájlnév nem maradhat üresen a normalizálás után.",
"attachments_and_uploads.filename_invalid_characters": "A fájlnév érvénytelen karaktereket tartalmaz.",
"attachments_and_uploads.filename_length_invalid": "A fájlnévnek {min} és {max} karakter között kell lennie.",
"attachments_and_uploads.image_size_exceeds_limit": "A kép mérete túl nagy, maximum {maxSize} bájt lehet.",
"attachments_and_uploads.invalid_file_field_name": "Érvénytelen fájlmezőnév: {key}. Elvárt formátum: `files[N]`, ahol N egy szám.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Fejezd be a fiókod beállítását, mielőtt jelentéseket küldhetnél.",
"moderation_and_reports.unknown_report": "Ismeretlen jelentés.",
"moderation_and_reports.user_is_not_banned": "Ez a felhasználó nincs kitiltva.",
"names_and_normalization.channel_name_empty_after_normalization": "Normalizálás után a csatornanév nem lehet üres.",
"names_and_normalization.name_empty_after_normalization": "Normalizálás után a név nem lehet üres.",
"permissions.global_ip_banned": "Az IP-címedet ({ipAddress}) a platform adminisztrátorai véglegesen letiltották a Fluxer API-ról. Ha úgy gondolod, hogy ez hiba, írj a [email protected] címre fellebbezéshez. Add meg ezt az IP-címet a fellebbezésben.",
"permissions.global_ip_temporarily_banned": "Az IP-címedet ({ipAddress}) visszaélésszerű vagy szokatlan hozzáférési minták miatt ideiglenesen, 24 órára letiltottuk a Fluxer API-ról. Ideiglenes API-tiltásokhoz általában nem biztosítunk fellebbezést. Válts IP-címet vagy várd meg a tiltás lejártát, és ellenőrizd a kliensedből érkező Fluxer API hozzáférési mintákat.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Indeks file {index} melebihi indeks maksimum yang diizinkan yaitu {maxIndex}.",
"attachments_and_uploads.file_not_found": "File tidak ditemukan.",
"attachments_and_uploads.file_size_too_large": "Ukuran file terlalu besar. Ukuran file maksimum adalah {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Nama file tidak boleh kosong setelah dinormalisasi.",
"attachments_and_uploads.filename_invalid_characters": "Nama file berisi karakter yang tidak valid.",
"attachments_and_uploads.filename_length_invalid": "Panjang nama file harus antara {min} dan {max} karakter.",
"attachments_and_uploads.image_size_exceeds_limit": "Ukuran gambar melebihi {maxSize} byte.",
"attachments_and_uploads.invalid_file_field_name": "Nama kolom file tidak valid: {key}. Format yang benar: `files[N]` dengan N adalah angka.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Kamu perlu menyelesaikan penyiapan akunmu sebelum bisa mengirim laporan.",
"moderation_and_reports.unknown_report": "Laporan tidak dikenal.",
"moderation_and_reports.user_is_not_banned": "Pengguna ini tidak di-banned.",
"names_and_normalization.channel_name_empty_after_normalization": "Nama kanal tidak bisa kosong setelah normalisasi.",
"names_and_normalization.name_empty_after_normalization": "Nama tidak bisa kosong setelah normalisasi.",
"permissions.global_ip_banned": "Alamat IP kamu {ipAddress} telah diblokir permanen dari Fluxer API oleh admin platform. Jika kamu merasa ini keliru, hubungi [email protected] untuk mengajukan banding. Sertakan alamat IP ini dalam banding kamu.",
"permissions.global_ip_temporarily_banned": "Alamat IP kamu {ipAddress} diblokir sementara dari Fluxer API selama 24 jam karena pola akses yang abusif atau tidak biasa. Biasanya kami tidak menyediakan banding untuk ban API sementara. Ganti alamat IP atau tunggu sampai ban berakhir, dan tinjau pola akses ke Fluxer API yang berasal dari klien kamu.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "L'indice del file {index} supera l'indice massimo consentito di {maxIndex}.",
"attachments_and_uploads.file_not_found": "File non trovato.",
"attachments_and_uploads.file_size_too_large": "La dimensione del file è troppo grande. La dimensione massima del file è {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Il nome del file non può essere vuoto dopo la normalizzazione.",
"attachments_and_uploads.filename_invalid_characters": "Il nome del file contiene caratteri non validi.",
"attachments_and_uploads.filename_length_invalid": "Il nome del file deve essere compreso tra {min} e {max} caratteri.",
"attachments_and_uploads.image_size_exceeds_limit": "La dimensione dell'immagine supera i {maxSize} byte.",
"attachments_and_uploads.invalid_file_field_name": "Nome del campo file non valido: {key}. Formato previsto: `files[N]` dove N è un numero.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Devi completare la configurazione dell'account prima di inviare segnalazioni.",
"moderation_and_reports.unknown_report": "Segnalazione sconosciuta.",
"moderation_and_reports.user_is_not_banned": "L'utente non è bannato.",
"names_and_normalization.channel_name_empty_after_normalization": "Il nome del canale non può essere vuoto dopo la normalizzazione.",
"names_and_normalization.name_empty_after_normalization": "Il nome non può essere vuoto dopo la normalizzazione.",
"permissions.global_ip_banned": "Il tuo indirizzo IP {ipAddress} è stato bloccato in modo permanente dall'API Fluxer dagli amministratori della piattaforma. Se pensi che sia un errore, contatta [email protected] per fare ricorso. Includi questo indirizzo IP nel ricorso.",
"permissions.global_ip_temporarily_banned": "Il tuo indirizzo IP {ipAddress} è stato bloccato temporaneamente dall'API Fluxer per 24 ore a causa di schemi di accesso abusivi o insoliti. Di solito non offriamo ricorsi per i ban temporanei dell'API. Cambia indirizzo IP o attendi la scadenza del ban, e controlla gli schemi di accesso all'API Fluxer provenienti dal tuo client.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "ファイルインデックス {index} が最大許容インデックス {maxIndex} を超えています。",
"attachments_and_uploads.file_not_found": "ファイルが見つかりません。",
"attachments_and_uploads.file_size_too_large": "ファイルサイズが大きすぎます。最大ファイルサイズは{maxSize}です。",
"attachments_and_uploads.filename_empty_after_normalization": "正規化後のファイル名は空にできません。",
"attachments_and_uploads.filename_invalid_characters": "ファイル名に無効な文字が含まれています。",
"attachments_and_uploads.filename_length_invalid": "ファイル名は{min}文字以上{max}文字以下で指定してください。",
"attachments_and_uploads.image_size_exceeds_limit": "画像サイズが{maxSize}バイトを超えています。",
"attachments_and_uploads.invalid_file_field_name": "無効なファイルフィールド名: {key}。`files[N]`(Nは数字)の形式にしてください。",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "レポートを送信する前に、アカウント設定を完了する必要があります。",
"moderation_and_reports.unknown_report": "不明なレポートです。",
"moderation_and_reports.user_is_not_banned": "このユーザーはBANされていません。",
"names_and_normalization.channel_name_empty_after_normalization": "正規化後、チャンネル名を空にできません。",
"names_and_normalization.name_empty_after_normalization": "正規化後、名前を空にできません。",
"permissions.global_ip_banned": "あなたのIPアドレス {ipAddress} は、プラットフォーム管理者によりFluxer APIから永久にブロックされました。誤りだと思う場合は、[email protected] に連絡して異議申し立てをしてください。申し立てにはこのIPアドレスを記載してください。",
"permissions.global_ip_temporarily_banned": "あなたのIPアドレス {ipAddress} は、不正または通常と異なるアクセスパターンのため、Fluxer APIから24時間一時的にブロックされました。一時的なAPI banについては通常、異議申し立てを受け付けていません。IPアドレスを変更するかbanの期限切れを待ち、クライアントからFluxer APIへのアクセスパターンを確認してください。",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "파일 인덱스 {index}가 최대 인덱스 {maxIndex}를 초과했어요.",
"attachments_and_uploads.file_not_found": "파일을 찾을 수 없어요.",
"attachments_and_uploads.file_size_too_large": "파일 크기가 너무 커요. 최대 파일 크기는 {maxSize}이에요.",
"attachments_and_uploads.filename_empty_after_normalization": "정규화 후 파일 이름은 비어 있으면 안 돼요.",
"attachments_and_uploads.filename_invalid_characters": "파일 이름에 유효하지 않은 문자가 포함되어 있어요.",
"attachments_and_uploads.filename_length_invalid": "파일 이름은 {min}자 이상 {max}자 이하여야 해요.",
"attachments_and_uploads.image_size_exceeds_limit": "이미지 크기가 {maxSize}바이트를 초과했어요.",
"attachments_and_uploads.invalid_file_field_name": "유효하지 않은 파일 필드 이름: {key}. 예상 형식: `files[N]` (N은 숫자).",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "신고를 제출하려면 계정 설정을 완료해야 해요.",
"moderation_and_reports.unknown_report": "알 수 없는 신고예요.",
"moderation_and_reports.user_is_not_banned": "이 사용자는 차단되지 않았어요.",
"names_and_normalization.channel_name_empty_after_normalization": "정규화 후 채널 이름은 비워둘 수 없어요.",
"names_and_normalization.name_empty_after_normalization": "정규화 후 이름은 비워둘 수 없어요.",
"permissions.global_ip_banned": "회원님의 IP 주소 {ipAddress}는 플랫폼 관리자가 Fluxer API에서 영구 차단했습니다. 오류라고 생각한다면 [email protected]으로 문의해 이의를 제기하세요. 이의 제기에는 이 IP 주소를 포함하세요.",
"permissions.global_ip_temporarily_banned": "회원님의 IP 주소 {ipAddress}는 악용 또는 비정상적인 접근 패턴으로 인해 Fluxer API에서 24시간 동안 일시 차단되었습니다. 일시적인 API 차단에는 보통 이의 제기를 제공하지 않습니다. IP 주소를 변경하거나 차단이 만료될 때까지 기다리고, 클라이언트에서 Fluxer API로 발생하는 접근 패턴을 검토하세요.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Failo indeksas {index} viršija didžiausią leistiną indeksą – {maxIndex}.",
"attachments_and_uploads.file_not_found": "Failas nerastas.",
"attachments_and_uploads.file_size_too_large": "Failo dydis per didelis. Didžiausias failo dydis yra {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Failo pavadinimas negali būti tuščias po normalizavimo.",
"attachments_and_uploads.filename_invalid_characters": "Failo pavadinime yra neleistinų simbolių.",
"attachments_and_uploads.filename_length_invalid": "Failo pavadinimas turi būti nuo {min} iki {max} simbolių.",
"attachments_and_uploads.image_size_exceeds_limit": "Paveikslėlio dydis viršija {maxSize} baitų.",
"attachments_and_uploads.invalid_file_field_name": "Neteisingas failo lauko pavadinimas: {key}. Tikėtinas formatas: `files[N]`, kur N yra skaičius.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Kad galėtum teikti ataskaitas, turi užbaigti paskyros nustatymą.",
"moderation_and_reports.unknown_report": "Nežinoma ataskaita.",
"moderation_and_reports.user_is_not_banned": "Šis vartotojas nėra banuotas.",
"names_and_normalization.channel_name_empty_after_normalization": "Kanalo pavadinimas negali būti tuščias po normalizavimo.",
"names_and_normalization.name_empty_after_normalization": "Pavadinimas negali būti tuščias po normalizavimo.",
"permissions.global_ip_banned": "Jūsų IP adresas {ipAddress} platformos administratorių buvo visam laikui užblokuotas Fluxer API. Jei manote, kad tai klaida, susisiekite su [email protected] dėl apeliacijos. Apeliacijoje nurodykite šį IP adresą.",
"permissions.global_ip_temporarily_banned": "Jūsų IP adresas {ipAddress} laikinai užblokuotas Fluxer API 24 valandoms dėl piktnaudžiavimo arba neįprastų prieigos modelių. Laikiniems API banams paprastai apeliacijų neteikiame. Pakeiskite IP adresą arba palaukite, kol banas baigsis, ir peržiūrėkite iš jūsų kliento ateinančius Fluxer API prieigos modelius.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Bestandsindex {index} overschrijdt de maximaal toegestane index van {maxIndex}.",
"attachments_and_uploads.file_not_found": "Bestand niet gevonden.",
"attachments_and_uploads.file_size_too_large": "Bestandsgrootte is te groot. Maximale bestandsgrootte is {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Bestandsnaam kan niet leeg zijn na normalisatie.",
"attachments_and_uploads.filename_invalid_characters": "Bestandsnaam bevat ongeldige tekens.",
"attachments_and_uploads.filename_length_invalid": "Bestandsnaam moet tussen {min} en {max} tekens zijn.",
"attachments_and_uploads.image_size_exceeds_limit": "Afbeeldingsgrootte overschrijdt {maxSize} bytes.",
"attachments_and_uploads.invalid_file_field_name": "Ongeldige bestandsveldnaam: {key}. Verwacht formaat: `files[N]` waarbij N een getal is.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Je moet je accountinstellingen voltooien voordat je meldingen kunt indienen.",
"moderation_and_reports.unknown_report": "Onbekend rapport.",
"moderation_and_reports.user_is_not_banned": "Deze gebruiker is niet verbannen.",
"names_and_normalization.channel_name_empty_after_normalization": "Kanaalnaam kan niet leeg zijn na normalisatie.",
"names_and_normalization.name_empty_after_normalization": "Naam kan niet leeg zijn na normalisatie.",
"permissions.global_ip_banned": "Je IP-adres {ipAddress} is door platformbeheerders permanent geblokkeerd voor de Fluxer API. Als je denkt dat dit een fout is, neem dan contact op met [email protected] om bezwaar te maken. Vermeld dit IP-adres in je bezwaar.",
"permissions.global_ip_temporarily_banned": "Je IP-adres {ipAddress} is tijdelijk 24 uur geblokkeerd voor de Fluxer API vanwege misbruikende of ongebruikelijke toegangspatronen. We bieden meestal geen bezwaarprocedure voor tijdelijke API-bans. Wijzig van IP-adres of wacht tot de ban verloopt, en controleer de toegangspatronen naar de Fluxer API vanuit je client.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Filindeks {index} overskrider maksimalt tillatt indeks på {maxIndex}.",
"attachments_and_uploads.file_not_found": "Filen ble ikke funnet.",
"attachments_and_uploads.file_size_too_large": "Filstørrelsen er for stor. Maksimal filstørrelse er {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Filnavnet kan ikke være tomt etter normalisering.",
"attachments_and_uploads.filename_invalid_characters": "Filnavnet inneholder ugyldige tegn.",
"attachments_and_uploads.filename_length_invalid": "Filnavnet må være mellom {min} og {max} tegn.",
"attachments_and_uploads.image_size_exceeds_limit": "Bildestørrelsen overskrider {maxSize} byte.",
"attachments_and_uploads.invalid_file_field_name": "Ugyldig filfeltnavn: {key}. Forventet format: `files[N]` der N er et tall.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Du må fullføre kontooppsettet ditt før du kan sende rapporter.",
"moderation_and_reports.unknown_report": "Ukjent rapport.",
"moderation_and_reports.user_is_not_banned": "Denne brukeren er ikke utestengt.",
"names_and_normalization.channel_name_empty_after_normalization": "Kanalnavnet kan ikke være tomt etter normalisering.",
"names_and_normalization.name_empty_after_normalization": "Navnet kan ikke være tomt etter normalisering.",
"permissions.global_ip_banned": "IP-adressen din {ipAddress} er permanent blokkert fra Fluxer API av plattformadministratorer. Hvis du mener dette er en feil, kontakt [email protected] for å klage. Ta med denne IP-adressen i klagen.",
"permissions.global_ip_temporarily_banned": "IP-adressen din {ipAddress} er midlertidig blokkert fra Fluxer API i 24 timer på grunn av misbrukende eller uvanlige tilgangsmønstre. Vi tilbyr vanligvis ikke klager for midlertidige API-bans. Bytt IP-adresse eller vent til bannen utløper, og gjennomgå tilgangsmønstrene til Fluxer API fra klienten din.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Indeks pliku {index} przekracza maksymalną wartość indeksu {maxIndex}.",
"attachments_and_uploads.file_not_found": "Nie znaleziono pliku.",
"attachments_and_uploads.file_size_too_large": "Rozmiar pliku jest za duży. Maksymalny rozmiar pliku wynosi {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Nazwa pliku nie może być pusta po normalizacji.",
"attachments_and_uploads.filename_invalid_characters": "Nazwa pliku zawiera nieprawidłowe znaki.",
"attachments_and_uploads.filename_length_invalid": "Nazwa pliku musi mieć od {min} do {max} znaków.",
"attachments_and_uploads.image_size_exceeds_limit": "Rozmiar obrazu przekracza {maxSize} bajtów.",
"attachments_and_uploads.invalid_file_field_name": "Nieprawidłowa nazwa pola pliku: {key}. Oczekiwany format: `files[N]`, gdzie N to liczba.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Dokończ konfigurację konta, zanim wyślesz zgłoszenia.",
"moderation_and_reports.unknown_report": "Nieznane zgłoszenie.",
"moderation_and_reports.user_is_not_banned": "Ten użytkownik nie ma bana.",
"names_and_normalization.channel_name_empty_after_normalization": "Nazwa kanału nie może być pusta po normalizacji.",
"names_and_normalization.name_empty_after_normalization": "Nazwa nie może być pusta po normalizacji.",
"permissions.global_ip_banned": "Twój adres IP {ipAddress} został trwale zablokowany w Fluxer API przez administratorów platformy. Jeśli uważasz, że to błąd, skontaktuj się z [email protected], aby się odwołać. Podaj ten adres IP w odwołaniu.",
"permissions.global_ip_temporarily_banned": "Twój adres IP {ipAddress} został tymczasowo zablokowany w Fluxer API na 24 godziny z powodu nadużyć lub nietypowych wzorców dostępu. Zazwyczaj nie oferujemy odwołań od tymczasowych banów API. Zmień adres IP albo poczekaj na wygaśnięcie bana i sprawdź wzorce dostępu do Fluxer API pochodzące z twojego klienta.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "O índice de arquivo {index} excede o índice máximo permitido de {maxIndex}.",
"attachments_and_uploads.file_not_found": "Arquivo não encontrado.",
"attachments_and_uploads.file_size_too_large": "O tamanho do arquivo é muito grande. O tamanho máximo do arquivo é {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "O nome do arquivo não pode estar vazio após a normalização.",
"attachments_and_uploads.filename_invalid_characters": "O nome do arquivo contém caracteres inválidos.",
"attachments_and_uploads.filename_length_invalid": "O nome do arquivo deve ter entre {min} e {max} caracteres.",
"attachments_and_uploads.image_size_exceeds_limit": "O tamanho da imagem excede {maxSize} bytes.",
"attachments_and_uploads.invalid_file_field_name": "Nome de campo de arquivo inválido: {key}. Formato esperado: `files[N]`, onde N é um número.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Você precisa terminar a configuração da sua conta antes de fazer denúncias.",
"moderation_and_reports.unknown_report": "Denúncia não encontrada.",
"moderation_and_reports.user_is_not_banned": "Este usuário não está banido.",
"names_and_normalization.channel_name_empty_after_normalization": "O nome do canal não pode estar vazio após a normalização.",
"names_and_normalization.name_empty_after_normalization": "O nome não pode ficar vazio após a normalização.",
"permissions.global_ip_banned": "Seu endereço IP {ipAddress} foi bloqueado permanentemente da API do Fluxer pelos administradores da plataforma. Se você acredita que isso é um erro, entre em contato com [email protected] para recorrer. Inclua este endereço IP no recurso.",
"permissions.global_ip_temporarily_banned": "Seu endereço IP {ipAddress} foi bloqueado temporariamente da API do Fluxer por 24 horas devido a padrões de acesso abusivos ou incomuns. Normalmente não oferecemos recursos para banimentos temporários da API. Troque de endereço IP ou aguarde o banimento expirar, e revise os padrões de acesso à API do Fluxer vindos do seu cliente.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Indexul fișierului {index} depășește indexul maxim permis de {maxIndex}.",
"attachments_and_uploads.file_not_found": "Fișierul nu a fost găsit.",
"attachments_and_uploads.file_size_too_large": "Dimensiunea fișierului este prea mare. Dimensiunea maximă a fișierului este {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Numele fișierului nu poate fi gol după normalizare.",
"attachments_and_uploads.filename_invalid_characters": "Numele fișierului conține caractere nevalide.",
"attachments_and_uploads.filename_length_invalid": "Numele fișierului trebuie să aibă între {min} și {max} caractere.",
"attachments_and_uploads.image_size_exceeds_limit": "Dimensiunea imaginii depășește {maxSize} octeți.",
"attachments_and_uploads.invalid_file_field_name": "Nume câmp fișier nevalid: {key}. Format așteptat: `files[N]` unde N este un număr.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Trebuie să-ți finalizezi configurarea contului înainte să poți trimite rapoarte.",
"moderation_and_reports.unknown_report": "Raportul este necunoscut.",
"moderation_and_reports.user_is_not_banned": "Acest utilizator nu e banat.",
"names_and_normalization.channel_name_empty_after_normalization": "Numele canalului nu poate fi gol după normalizare.",
"names_and_normalization.name_empty_after_normalization": "Numele nu poate fi gol după normalizare.",
"permissions.global_ip_banned": "Adresa ta IP {ipAddress} a fost blocată permanent din Fluxer API de administratorii platformei. Dacă crezi că este o greșeală, contactează [email protected] pentru apel. Include această adresă IP în apel.",
"permissions.global_ip_temporarily_banned": "Adresa ta IP {ipAddress} a fost blocată temporar din Fluxer API timp de 24 de ore din cauza unor tipare de acces abuzive sau neobișnuite. De obicei nu oferim apeluri pentru banurile temporare API. Schimbă adresa IP sau așteaptă expirarea banului și verifică tiparele de acces la Fluxer API provenite din clientul tău.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Индекс файла {index} превышает максимальный индекс {maxIndex}.",
"attachments_and_uploads.file_not_found": "Файл не найден.",
"attachments_and_uploads.file_size_too_large": "Размер файла слишком большой. Максимальный размер файла: {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Имя файла не может быть пустым после нормализации.",
"attachments_and_uploads.filename_invalid_characters": "Имя файла содержит недопустимые символы.",
"attachments_and_uploads.filename_length_invalid": "Имя файла должно содержать от {min} до {max} символов.",
"attachments_and_uploads.image_size_exceeds_limit": "Размер изображения превышает {maxSize} байт.",
"attachments_and_uploads.invalid_file_field_name": "Недопустимое имя поля файла: {key}. Ожидаемый формат: `files[N]`, где N — число.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Тебе нужно завершить настройку аккаунта, чтобы подавать жалобы.",
"moderation_and_reports.unknown_report": "Жалоба не найдена.",
"moderation_and_reports.user_is_not_banned": "Этот пользователь не забанен.",
"names_and_normalization.channel_name_empty_after_normalization": "Название канала не может быть пустым после обработки.",
"names_and_normalization.name_empty_after_normalization": "Имя не может быть пустым после обработки.",
"permissions.global_ip_banned": "Ваш IP-адрес {ipAddress} был навсегда заблокирован в Fluxer API администраторами платформы. Если вы считаете, что это ошибка, обратитесь на [email protected] для апелляции. Укажите этот IP-адрес в апелляции.",
"permissions.global_ip_temporarily_banned": "Ваш IP-адрес {ipAddress} временно заблокирован в Fluxer API на 24 часа из-за злоупотребляющих или необычных шаблонов доступа. Обычно мы не принимаем апелляции по временным API-банам. Смените IP-адрес или дождитесь окончания бана и проверьте шаблоны доступа к Fluxer API, поступающие от вашего клиента.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Filindex {index} överskrider det högsta tillåtna indexet på {maxIndex}.",
"attachments_and_uploads.file_not_found": "Filen hittades inte.",
"attachments_and_uploads.file_size_too_large": "Filstorleken är för stor. Max filstorlek är {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Filnamnet kan inte vara tomt efter normalisering.",
"attachments_and_uploads.filename_invalid_characters": "Filnamnet innehåller ogiltiga tecken.",
"attachments_and_uploads.filename_length_invalid": "Filnamnet måste vara mellan {min} och {max} tecken.",
"attachments_and_uploads.image_size_exceeds_limit": "Bildstorleken överskrider {maxSize} byte.",
"attachments_and_uploads.invalid_file_field_name": "Ogiltigt filfältnamn: {key}. Förväntat format: `files[N]` där N är en siffra.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Du måste slutföra dina kontoinställningar innan du kan skicka in rapporter.",
"moderation_and_reports.unknown_report": "Okänd rapport.",
"moderation_and_reports.user_is_not_banned": "Denna användare är inte bannad.",
"names_and_normalization.channel_name_empty_after_normalization": "Kanalnamnet kan inte vara tomt efter att ha normaliserats.",
"names_and_normalization.name_empty_after_normalization": "Namnet kan inte vara tomt efter att ha normaliserats.",
"permissions.global_ip_banned": "Din IP-adress {ipAddress} har blockerats permanent från Fluxer API av plattformsadministratörer. Om du tror att detta är ett misstag, kontakta [email protected] för att överklaga. Ta med den här IP-adressen i överklagan.",
"permissions.global_ip_temporarily_banned": "Din IP-adress {ipAddress} har blockerats tillfälligt från Fluxer API i 24 timmar på grund av missbrukande eller ovanliga åtkomstmönster. Vi erbjuder vanligtvis inte överklaganden för tillfälliga API-bans. Byt IP-adress eller vänta tills bannen löper ut och granska åtkomstmönstren till Fluxer API från din klient.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "ดัชนีไฟล์ {index} เกินค่าสูงสุดที่อนุญาตคือ {maxIndex}",
"attachments_and_uploads.file_not_found": "ไม่พบไฟล์",
"attachments_and_uploads.file_size_too_large": "ขนาดไฟล์ใหญ่เกินไป ขนาดไฟล์สูงสุดคือ {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "ชื่อไฟล์ต้องไม่ว่างเปล่าหลังการปรับให้เป็นมาตรฐาน.",
"attachments_and_uploads.filename_invalid_characters": "ชื่อไฟล์มีอักขระที่ไม่ถูกต้อง.",
"attachments_and_uploads.filename_length_invalid": "ชื่อไฟล์ต้องมี {min} ถึง {max} ตัวอักษร.",
"attachments_and_uploads.image_size_exceeds_limit": "ขนาดรูปภาพเกิน {maxSize} ไบต์.",
"attachments_and_uploads.invalid_file_field_name": "ชื่อฟิลด์ไฟล์ไม่ถูกต้อง: {key} รูปแบบที่คาดไว้: `files[N]` โดยที่ N คือตัวเลข.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "คุณต้องตั้งค่าบัญชีให้เสร็จสมบูรณ์ก่อนจึงจะสามารถส่งรายงานได้",
"moderation_and_reports.unknown_report": "ไม่พบรายงาน",
"moderation_and_reports.user_is_not_banned": "ผู้ใช้รายนี้ไม่ได้ถูกแบน",
"names_and_normalization.channel_name_empty_after_normalization": "ชื่อช่องต้องไม่ว่างเปล่าหลังจากการปรับให้เป็นมาตรฐาน",
"names_and_normalization.name_empty_after_normalization": "ชื่อต้องไม่ว่างเปล่าหลังจากการปรับให้เป็นมาตรฐาน",
"permissions.global_ip_banned": "ที่อยู่ IP ของคุณ {ipAddress} ถูกบล็อกจาก Fluxer API อย่างถาวรโดยผู้ดูแลแพลตฟอร์ม หากคุณเชื่อว่านี่เป็นข้อผิดพลาด โปรดติดต่อ [email protected] เพื่ออุทธรณ์ โปรดแนบที่อยู่ IP นี้ในการอุทธรณ์",
"permissions.global_ip_temporarily_banned": "ที่อยู่ IP ของคุณ {ipAddress} ถูกบล็อกจาก Fluxer API ชั่วคราวเป็นเวลา 24 ชั่วโมงเนื่องจากรูปแบบการเข้าถึงที่ผิดปกติหรือมีลักษณะละเมิด โดยปกติเราไม่รับอุทธรณ์สำหรับการแบน API ชั่วคราว โปรดเปลี่ยนที่อยู่ IP หรือรอให้การแบนหมดอายุ และตรวจสอบรูปแบบการเข้าถึง Fluxer API ที่มาจากไคลเอนต์ของคุณ",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Dosya indeksi {index}, izin verilen maksimum indeks olan {maxIndex} değerini aşıyor.",
"attachments_and_uploads.file_not_found": "Dosya bulunamadı.",
"attachments_and_uploads.file_size_too_large": "Dosya boyutu çok büyük. İzin verilen maksimum dosya boyutu {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Normalizasyondan sonra dosya adını boş bırakamazsın.",
"attachments_and_uploads.filename_invalid_characters": "Dosya adında geçersiz karakterler var.",
"attachments_and_uploads.filename_length_invalid": "Dosya adı {min} ile {max} karakter arasında olmalı.",
"attachments_and_uploads.image_size_exceeds_limit": "Görsel boyutu {maxSize} baytı aşıyor.",
"attachments_and_uploads.invalid_file_field_name": "Geçersiz dosya alan adı: {key}. Beklenen biçim: `files[N]` (N, bir sayı).",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Rapor bildirmeden önce hesap kurulumunu tamamlamalısın.",
"moderation_and_reports.unknown_report": "Bilinmeyen rapor.",
"moderation_and_reports.user_is_not_banned": "Bu kullanıcı yasaklı değil.",
"names_and_normalization.channel_name_empty_after_normalization": "Kanal adı düzenlendikten sonra boş bırakılamaz.",
"names_and_normalization.name_empty_after_normalization": "Ad düzenlendikten sonra boş bırakılamaz.",
"permissions.global_ip_banned": "IP adresiniz {ipAddress}, platform yöneticileri tarafından Fluxer API'den kalıcı olarak engellendi. Bunun bir hata olduğunu düşünüyorsanız itiraz etmek için [email protected] ile iletişime geçin. İtirazınıza bu IP adresini ekleyin.",
"permissions.global_ip_temporarily_banned": "IP adresiniz {ipAddress}, kötüye kullanıma benzeyen veya olağan dışı erişim kalıpları nedeniyle Fluxer API'den 24 saatliğine geçici olarak engellendi. Geçici API banları için genellikle itiraz sunmuyoruz. IP adresinizi değiştirin veya banın süresinin dolmasını bekleyin ve istemcinizden Fluxer API'ye gelen erişim kalıplarını gözden geçirin.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Індекс файлу {index} перевищує максимальний індекс {maxIndex}.",
"attachments_and_uploads.file_not_found": "Файл не знайдено.",
"attachments_and_uploads.file_size_too_large": "Розмір файлу завеликий. Максимальний розмір файлу: {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Ім'я файлу не може бути порожнім після нормалізації.",
"attachments_and_uploads.filename_invalid_characters": "Ім'я файлу містить неприпустимі символи.",
"attachments_and_uploads.filename_length_invalid": "Ім'я файлу має містити від {min} до {max} символів.",
"attachments_and_uploads.image_size_exceeds_limit": "Розмір зображення перевищує {maxSize} байтів.",
"attachments_and_uploads.invalid_file_field_name": "Некоректне ім'я поля файлу: {key}. Очікуваний формат: `files[N]`, де N — число.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Тобі потрібно завершити налаштування акаунта, щоб надсилати звіти.",
"moderation_and_reports.unknown_report": "Звіт не знайдено.",
"moderation_and_reports.user_is_not_banned": "Цей користувач не забанений.",
"names_and_normalization.channel_name_empty_after_normalization": "Назва каналу не може бути порожньою після нормалізації.",
"names_and_normalization.name_empty_after_normalization": "Ім'я не може бути порожнім після нормалізації.",
"permissions.global_ip_banned": "Вашу IP-адресу {ipAddress} адміністратори платформи назавжди заблокували у Fluxer API. Якщо ви вважаєте, що це помилка, зверніться на [email protected] для апеляції. Вкажіть цю IP-адресу в апеляції.",
"permissions.global_ip_temporarily_banned": "Вашу IP-адресу {ipAddress} тимчасово заблоковано у Fluxer API на 24 години через зловживальні або незвичні шаблони доступу. Зазвичай ми не надаємо апеляції для тимчасових API-банів. Змініть IP-адресу або дочекайтеся завершення бану та перегляньте шаблони доступу до Fluxer API, що надходять від вашого клієнта.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "Chỉ mục tệp {index} vượt quá chỉ mục tối đa cho phép là {maxIndex}.",
"attachments_and_uploads.file_not_found": "Không tìm thấy tệp.",
"attachments_and_uploads.file_size_too_large": "Kích thước tệp quá lớn. Tối đa {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Tên tệp không được để trống sau khi chuẩn hóa.",
"attachments_and_uploads.filename_invalid_characters": "Tên tệp có ký tự không hợp lệ.",
"attachments_and_uploads.filename_length_invalid": "Tên tệp phải có độ dài từ {min} đến {max} ký tự.",
"attachments_and_uploads.image_size_exceeds_limit": "Kích thước hình ảnh lớn hơn {maxSize} byte.",
"attachments_and_uploads.invalid_file_field_name": "Tên trường tệp không hợp lệ: {key}. Định dạng đúng: `files[N]` trong đó N là một số.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "Bạn cần hoàn tất thiết lập tài khoản trước khi có thể gửi báo cáo.",
"moderation_and_reports.unknown_report": "Báo cáo không xác định.",
"moderation_and_reports.user_is_not_banned": "Người dùng này không bị ban.",
"names_and_normalization.channel_name_empty_after_normalization": "Tên kênh không thể trống sau khi chuẩn hóa.",
"names_and_normalization.name_empty_after_normalization": "Tên không thể trống sau khi chuẩn hóa.",
"permissions.global_ip_banned": "Địa chỉ IP của bạn {ipAddress} đã bị quản trị viên nền tảng chặn vĩnh viễn khỏi Fluxer API. Nếu bạn cho rằng đây là nhầm lẫn, hãy liên hệ [email protected] để kháng nghị. Hãy kèm địa chỉ IP này trong kháng nghị.",
"permissions.global_ip_temporarily_banned": "Địa chỉ IP của bạn {ipAddress} đã bị chặn tạm thời khỏi Fluxer API trong 24 giờ do các mẫu truy cập lạm dụng hoặc bất thường. Chúng tôi thường không hỗ trợ kháng nghị cho các lệnh ban API tạm thời. Hãy đổi địa chỉ IP hoặc chờ lệnh ban hết hạn, và xem lại các mẫu truy cập Fluxer API đến từ client của bạn.",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "文件索引 {index} 超过了允许的最大索引 {maxIndex}。",
"attachments_and_uploads.file_not_found": "文件未找到。",
"attachments_and_uploads.file_size_too_large": "文件大小过大。最大文件大小为 {maxSize}。",
"attachments_and_uploads.filename_empty_after_normalization": "规范化后文件名不可为空。",
"attachments_and_uploads.filename_invalid_characters": "文件名包含无效字符。",
"attachments_and_uploads.filename_length_invalid": "文件名必须在 {min} 到 {max} 个字符之间。",
"attachments_and_uploads.image_size_exceeds_limit": "图片大小超出 {maxSize} 字节。",
"attachments_and_uploads.invalid_file_field_name": "无效的文件字段名称:{key}。预期格式:`files[N]`,其中 N 为数字。",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "你需要完成账号设置才能提交举报。",
"moderation_and_reports.unknown_report": "未知举报。",
"moderation_and_reports.user_is_not_banned": "此用户未被封禁。",
"names_and_normalization.channel_name_empty_after_normalization": "规范化后频道名称不能为空。",
"names_and_normalization.name_empty_after_normalization": "规范化后名称不能为空。",
"permissions.global_ip_banned": "你的 IP 地址 {ipAddress} 已被平台管理员永久禁止访问 Fluxer API。如果你认为这是误判,请联系 [email protected] 申诉。请在申诉中附上此 IP 地址。",
"permissions.global_ip_temporarily_banned": "你的 IP 地址 {ipAddress} 因存在滥用或异常访问模式,已被临时禁止访问 Fluxer API 24 小时。临时 API ban 通常不提供申诉。请更换 IP 地址或等待 ban 过期,并检查你的客户端对 Fluxer API 的访问模式。",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "檔案索引 {index} 超過允許的最大索引 {maxIndex}。",
"attachments_and_uploads.file_not_found": "找不到檔案。",
"attachments_and_uploads.file_size_too_large": "檔案大小過大。最大檔案大小為 {maxSize}。",
"attachments_and_uploads.filename_empty_after_normalization": "正規化後檔案名稱不能為空。",
"attachments_and_uploads.filename_invalid_characters": "檔案名稱包含無效字元。",
"attachments_and_uploads.filename_length_invalid": "檔案名稱必須介於 {min} 到 {max} 個字元之間。",
"attachments_and_uploads.image_size_exceeds_limit": "圖片大小超過 {maxSize} 位元組。",
"attachments_and_uploads.invalid_file_field_name": "無效的檔案欄位名稱:{key}。預期格式為 `files[N]`,其中 N 為數字。",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "你必須完成帳號設定才能送出檢舉。",
"moderation_and_reports.unknown_report": "不明的檢舉。",
"moderation_and_reports.user_is_not_banned": "此使用者未被封鎖。",
"names_and_normalization.channel_name_empty_after_normalization": "正規化後頻道名稱不能為空。",
"names_and_normalization.name_empty_after_normalization": "正規化後名稱不可為空。",
"permissions.global_ip_banned": "你的 IP 位址 {ipAddress} 已被平台管理員永久禁止存取 Fluxer API。如果你認為這是誤判,請聯絡 [email protected] 申訴。請在申訴中附上此 IP 位址。",
"permissions.global_ip_temporarily_banned": "你的 IP 位址 {ipAddress} 因濫用或異常存取模式,已被暫時禁止存取 Fluxer API 24 小時。暫時 API ban 通常不提供申訴。請更換 IP 位址或等待 ban 到期,並檢查你的用戶端對 Fluxer API 的存取模式。",
@@ -59,8 +59,6 @@
"attachments_and_uploads.file_index_exceeds_maximum": "File index {index} exceeds the maximum allowed index of {maxIndex}.",
"attachments_and_uploads.file_not_found": "File not found.",
"attachments_and_uploads.file_size_too_large": "File size is too large. Maximum file size is {maxSize}.",
"attachments_and_uploads.filename_empty_after_normalization": "Filename can't be empty after normalization.",
"attachments_and_uploads.filename_invalid_characters": "Filename contains invalid characters.",
"attachments_and_uploads.filename_length_invalid": "Filename must be between {min} and {max} characters.",
"attachments_and_uploads.image_size_exceeds_limit": "Image size exceeds {maxSize} bytes.",
"attachments_and_uploads.invalid_file_field_name": "Invalid file field name: {key}. Expected format: `files[N]` where N is a number.",
@@ -368,7 +366,6 @@
"moderation_and_reports.unclaimed_account_cannot_submit_reports": "You need to complete your account setup before you can submit reports.",
"moderation_and_reports.unknown_report": "Unknown report.",
"moderation_and_reports.user_is_not_banned": "This user isn't banned.",
"names_and_normalization.channel_name_empty_after_normalization": "Channel name can't be empty after normalization.",
"names_and_normalization.name_empty_after_normalization": "Name can't be empty after normalization.",
"permissions.global_ip_banned": "Your IP address {ipAddress} has been permanently blocked from the Fluxer API by platform administrators. If you believe this is a mistake, contact [email protected] to appeal. Include this IP address in your appeal.",
"permissions.global_ip_temporarily_banned": "Your IP address {ipAddress} has been temporarily blocked from the Fluxer API for 24 hours because of abusive or unusual access patterns. We usually do not provide appeals for temporary API bans. Change IP addresses or wait for the ban to expire, and review the Fluxer API access patterns coming from your client.",
+1 -10
View File
@@ -1,18 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import tsconfigPaths from 'vite-tsconfig-paths';
import {defineConfig} from 'vitest/config';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default defineConfig({
plugins: [
tsconfigPaths({
root: path.resolve(__dirname, '../..'),
}),
],
resolve: {tsconfigPaths: true},
test: {
globals: true,
environment: 'node',
-1
View File
@@ -17,7 +17,6 @@
"devDependencies": {
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"vite-tsconfig-paths": "catalog:",
"vitest": "catalog:"
}
}
+1 -10
View File
@@ -1,18 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import tsconfigPaths from 'vite-tsconfig-paths';
import {defineConfig} from 'vitest/config';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default defineConfig({
plugins: [
tsconfigPaths({
root: path.resolve(__dirname, '../..'),
}),
],
resolve: {tsconfigPaths: true},
test: {
globals: true,
environment: 'node',
-1
View File
@@ -20,7 +20,6 @@
"devDependencies": {
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"vite-tsconfig-paths": "catalog:",
"vitest": "catalog:"
}
}

Some files were not shown because too many files have changed in this diff Show More