mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 04:55:42 +09:00
chore(repo): remove dead code and useless tests (#3375)
This commit is contained in:
1 parent
7e177a1c90
commit
1cd62eb661
1391 files changed
+783
-120415
No files matched your search
@@ -785,13 +785,6 @@ export async function loadConfig(): Promise<MasterConfig> {
|
||||
return cachedConfig;
|
||||
}
|
||||
|
||||
export function getConfig(): MasterConfig {
|
||||
if (!cachedConfig) {
|
||||
throw new Error('Config not loaded. Call loadConfig() first.');
|
||||
}
|
||||
return cachedConfig;
|
||||
}
|
||||
|
||||
export function resetConfig(): void {
|
||||
cachedConfig = null;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import {generateKeyPairSync} from 'node:crypto';
|
||||
import {mkdtempSync, rmSync, writeFileSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join} from 'node:path';
|
||||
import {getConfig, loadConfig, resetConfig} from '@fluxer/config/src/ConfigLoader';
|
||||
import {loadConfig, resetConfig} from '@fluxer/config/src/ConfigLoader';
|
||||
import {afterEach, beforeEach, describe, expect, test, vi} from 'vitest';
|
||||
|
||||
const MINIMAL_ENV: Record<string, string> = {
|
||||
@@ -101,25 +101,6 @@ describe('ConfigLoader', () => {
|
||||
await expect(loadConfig()).rejects.toThrow('FLUXER_SUDO_MODE_SECRET and FLUXER_SUDO_MODE_SECRET_FILE are both set');
|
||||
});
|
||||
|
||||
test('getConfig throws when config is not loaded', () => {
|
||||
expect(() => getConfig()).toThrow('Config not loaded');
|
||||
});
|
||||
|
||||
test('resetConfig clears the cache', async () => {
|
||||
stubMinimalEnv();
|
||||
await loadConfig();
|
||||
expect(() => getConfig()).not.toThrow();
|
||||
resetConfig();
|
||||
expect(() => getConfig()).toThrow('Config not loaded');
|
||||
});
|
||||
|
||||
test('derives endpoints from domain config', async () => {
|
||||
stubMinimalEnv();
|
||||
const config = await loadConfig();
|
||||
expect(config.endpoints.api).toBe('http://localhost:8088/api');
|
||||
expect(config.endpoints.gateway).toBe('ws://localhost:8088/gateway');
|
||||
});
|
||||
|
||||
test('endpoint overrides take precedence over derived endpoints', async () => {
|
||||
stubMinimalEnv({
|
||||
FLUXER_API_ENDPOINT: 'https://custom-api.example.com',
|
||||
@@ -312,22 +293,6 @@ describe('ConfigLoader', () => {
|
||||
expect(config.services.api.storage_change_feed?.skip_buckets).toBeUndefined();
|
||||
});
|
||||
|
||||
test('reads the email reply-to address', async () => {
|
||||
stubMinimalEnv({FLUXER_EMAIL_REPLY_TO_EMAIL: '[email protected]'});
|
||||
|
||||
const config = await loadConfig();
|
||||
|
||||
expect(config.integrations.email.reply_to_email).toBe('[email protected]');
|
||||
});
|
||||
|
||||
test('leaves the email reply-to address empty when unset or blank', async () => {
|
||||
stubMinimalEnv({FLUXER_EMAIL_REPLY_TO_EMAIL: ' '});
|
||||
|
||||
const config = await loadConfig();
|
||||
|
||||
expect(config.integrations.email.reply_to_email).toBe('');
|
||||
});
|
||||
|
||||
test.each(['support', 'Support <[email protected]>', '[email protected],[email protected]', ' [email protected]'])(
|
||||
'rejects %j as the email reply-to address',
|
||||
async (value) => {
|
||||
@@ -381,25 +346,6 @@ describe('ConfigLoader', () => {
|
||||
expect(config.integrations.voice.default_region?.id).toBe('local');
|
||||
});
|
||||
|
||||
test('ignores FLUXER_GATEWAY_PUSH_ENABLED, which only the gateway reads', async () => {
|
||||
stubMinimalEnv({FLUXER_GATEWAY_PUSH_ENABLED: 'false'});
|
||||
|
||||
const config = await loadConfig();
|
||||
|
||||
expect(config.services.gateway).not.toHaveProperty('push_enabled');
|
||||
});
|
||||
|
||||
test('accepts the crosspost worker lane', async () => {
|
||||
stubMinimalEnv({
|
||||
FLUXER_API_WORKER_MODE: 'single_lane',
|
||||
FLUXER_API_WORKER_LANE: 'crosspost',
|
||||
FLUXER_API_WORKER_LANE_CONCURRENCY_OVERRIDES: '{"crosspost":4}',
|
||||
});
|
||||
const config = await loadConfig();
|
||||
expect(config.services.api.worker?.lane).toBe('crosspost');
|
||||
expect(config.services.api.worker?.lane_concurrency_overrides?.crosspost).toBe(4);
|
||||
});
|
||||
|
||||
test('rejects an unknown worker lane', async () => {
|
||||
stubMinimalEnv({FLUXER_API_WORKER_MODE: 'single_lane', FLUXER_API_WORKER_LANE: 'publishing'});
|
||||
await expect(loadConfig()).rejects.toThrow('FLUXER_API_WORKER_LANE');
|
||||
@@ -410,26 +356,6 @@ describe('ConfigLoader', () => {
|
||||
await expect(loadConfig()).rejects.toThrow('FLUXER_API_WORKER_TASK');
|
||||
});
|
||||
|
||||
test('leaves the storage change feed disabled by default', async () => {
|
||||
stubMinimalEnv();
|
||||
const config = await loadConfig();
|
||||
expect(config.services.api.storage_change_feed).toEqual({enabled: false, stream: 'STORAGE_CHANGES'});
|
||||
});
|
||||
|
||||
test('maps the storage change feed environment variables', async () => {
|
||||
stubMinimalEnv({
|
||||
FLUXER_API_STORAGE_CHANGE_FEED_ENABLED: 'true',
|
||||
FLUXER_API_STORAGE_CHANGE_FEED_STREAM: 'BACKUP_CHANGES',
|
||||
FLUXER_API_STORAGE_CHANGE_FEED_SKIP_BUCKETS: 'fluxer-uploads, fluxer-harvests',
|
||||
});
|
||||
const config = await loadConfig();
|
||||
expect(config.services.api.storage_change_feed).toEqual({
|
||||
enabled: true,
|
||||
stream: 'BACKUP_CHANGES',
|
||||
skip_buckets: ['fluxer-uploads', 'fluxer-harvests'],
|
||||
});
|
||||
});
|
||||
|
||||
test('rejects a storage change feed stream name that JetStream cannot use', async () => {
|
||||
stubMinimalEnv({
|
||||
FLUXER_API_STORAGE_CHANGE_FEED_ENABLED: 'true',
|
||||
@@ -453,50 +379,11 @@ describe('ConfigLoader', () => {
|
||||
await expect(loadConfig()).rejects.toThrow('FLUXER_LIVEKIT_DEFAULT_REGION must be valid JSON');
|
||||
});
|
||||
|
||||
test('keeps Postgres prepared statements on by default', async () => {
|
||||
stubMinimalEnv();
|
||||
expect((await loadConfig()).database.postgres.prepared_statements).toBe(true);
|
||||
});
|
||||
|
||||
test('rejects a non-boolean Postgres prepared statements value', async () => {
|
||||
stubMinimalEnv({FLUXER_POSTGRES_PREPARED_STATEMENTS: 'maybe'});
|
||||
await expect(loadConfig()).rejects.toThrow('FLUXER_POSTGRES_PREPARED_STATEMENTS');
|
||||
});
|
||||
|
||||
test('keeps the api http timeouts at their defaults', async () => {
|
||||
stubMinimalEnv();
|
||||
const config = await loadConfig();
|
||||
expect(config.services.api.headers_timeout_ms).toBe(30_000);
|
||||
expect(config.services.api.request_timeout_ms).toBe(120_000);
|
||||
});
|
||||
|
||||
test('reads the api http timeouts from the environment', async () => {
|
||||
stubMinimalEnv({FLUXER_API_HEADERS_TIMEOUT_MS: '45000', FLUXER_API_REQUEST_TIMEOUT_MS: '600000'});
|
||||
const config = await loadConfig();
|
||||
expect(config.services.api.headers_timeout_ms).toBe(45_000);
|
||||
expect(config.services.api.request_timeout_ms).toBe(600_000);
|
||||
});
|
||||
|
||||
test('rejects a non-numeric api header timeout', async () => {
|
||||
stubMinimalEnv({FLUXER_API_HEADERS_TIMEOUT_MS: 'soon'});
|
||||
await expect(loadConfig()).rejects.toThrow('FLUXER_API_HEADERS_TIMEOUT_MS');
|
||||
});
|
||||
|
||||
test('rejects a non-numeric api request timeout', async () => {
|
||||
stubMinimalEnv({FLUXER_API_REQUEST_TIMEOUT_MS: 'soon'});
|
||||
await expect(loadConfig()).rejects.toThrow('FLUXER_API_REQUEST_TIMEOUT_MS');
|
||||
});
|
||||
|
||||
test('applies the KV mode from the environment', async () => {
|
||||
stubMinimalEnv({FLUXER_KV_MODE: 'cluster'});
|
||||
expect((await loadConfig()).internal.kv_mode).toBe('cluster');
|
||||
});
|
||||
|
||||
test('rejects an unknown KV mode', async () => {
|
||||
stubMinimalEnv({FLUXER_KV_MODE: 'sentinel'});
|
||||
await expect(loadConfig()).rejects.toThrow('Invalid FLUXER_KV_MODE: sentinel');
|
||||
});
|
||||
|
||||
test('reads the profile pseudonym secret and requires it in production', async () => {
|
||||
stubMinimalEnv();
|
||||
expect((await loadConfig()).auth.profile_pseudonym_secret).toBe('test-profile-pseudonym-secret');
|
||||
@@ -567,56 +454,6 @@ describe('ConfigLoader', () => {
|
||||
await expect(loadConfig()).rejects.toThrow('FLUXER_POSTGRES_SSL must be true');
|
||||
});
|
||||
|
||||
test('parses self-host branding, setup, and search engine environment variables', async () => {
|
||||
stubMinimalEnv({
|
||||
FLUXER_SEARCH_ENGINE: 'meilisearch',
|
||||
FLUXER_SEARCH_URL: 'http://meilisearch:7700',
|
||||
FLUXER_SEARCH_API_KEY: 'meili-key',
|
||||
FLUXER_SELF_HOSTED: 'true',
|
||||
FLUXER_APP_PRODUCT_NAME: 'Example Chat',
|
||||
FLUXER_APP_ICON_URL: 'https://assets.example/icon.png',
|
||||
FLUXER_APP_SYMBOL_URL: 'https://assets.example/symbol.png',
|
||||
FLUXER_APP_LOGO_URL: 'https://assets.example/logo.png',
|
||||
FLUXER_APP_WORDMARK_URL: 'https://assets.example/wordmark.png',
|
||||
FLUXER_APP_FAVICON_URL: 'https://assets.example/favicon.png',
|
||||
FLUXER_APP_THEME_COLOR: '#123456',
|
||||
FLUXER_APP_STATUS_PAGE_URL: 'https://status.example',
|
||||
FLUXER_APP_STATUS_PAGE_INCIDENT_HISTORY_URL: 'https://status.example/history',
|
||||
FLUXER_INSTANCE_SETUP_CONFIGURED: 'true',
|
||||
});
|
||||
|
||||
const config = await loadConfig();
|
||||
|
||||
expect(config.integrations.search.engine).toBe('meilisearch');
|
||||
expect(config.integrations.search.url).toBe('http://meilisearch:7700');
|
||||
expect(config.integrations.search.api_key).toBe('meili-key');
|
||||
expect(config.instance.self_hosted).toBe(true);
|
||||
expect(config.instance.branding).toEqual({
|
||||
product_name: 'Example Chat',
|
||||
icon_url: 'https://assets.example/icon.png',
|
||||
symbol_url: 'https://assets.example/symbol.png',
|
||||
logo_url: 'https://assets.example/logo.png',
|
||||
wordmark_url: 'https://assets.example/wordmark.png',
|
||||
favicon_url: 'https://assets.example/favicon.png',
|
||||
theme_color: '#123456',
|
||||
status_page_url: 'https://status.example',
|
||||
status_page_incident_history_url: 'https://status.example/history',
|
||||
});
|
||||
expect(config.instance.setup.configured).toBe(true);
|
||||
});
|
||||
|
||||
test('leaves the account identity unset by default', async () => {
|
||||
stubMinimalEnv();
|
||||
const config = await loadConfig();
|
||||
expect(config.instance.account_identity).toBeNull();
|
||||
});
|
||||
|
||||
test('treats an empty account identity as unset', async () => {
|
||||
stubMinimalEnv({FLUXER_ACCOUNT_IDENTITY: ' '});
|
||||
const config = await loadConfig();
|
||||
expect(config.instance.account_identity).toBeNull();
|
||||
});
|
||||
|
||||
test.each([
|
||||
['email', 'email'],
|
||||
['username', 'username'],
|
||||
@@ -647,19 +484,6 @@ describe('ConfigLoader', () => {
|
||||
await expect(loadConfig()).rejects.toThrow('FLUXER_ACCOUNT_IDENTITY must be email or username');
|
||||
});
|
||||
|
||||
test('leaves both stores off with no apps, packages or products by default', async () => {
|
||||
stubMinimalEnv();
|
||||
const config = await loadConfig();
|
||||
expect(config.integrations.app_store).toEqual({enabled: false, apps: [], products: {}});
|
||||
expect(config.integrations.google_play).toEqual({
|
||||
enabled: false,
|
||||
packages: [],
|
||||
token_uri: 'https://oauth2.googleapis.com/token',
|
||||
products: {},
|
||||
});
|
||||
expect(config.integrations.store_billing).toEqual({sandbox_user_ids: [], sandbox_entitles_all: false});
|
||||
});
|
||||
|
||||
test('loads a valid store catalogue', async () => {
|
||||
stubMinimalEnv({
|
||||
FLUXER_APP_STORE_ENABLED: 'true',
|
||||
@@ -727,27 +551,6 @@ describe('ConfigLoader', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('defaults the cache purge adapter to none', async () => {
|
||||
stubMinimalEnv();
|
||||
expect((await loadConfig()).integrations.cache_purge).toEqual({
|
||||
adapter: 'none',
|
||||
http: {endpoint: '', token: '', timeout_ms: 10_000},
|
||||
});
|
||||
});
|
||||
|
||||
test('reads the http cache purge settings from the environment', async () => {
|
||||
stubMinimalEnv({
|
||||
FLUXER_CACHE_PURGE_ADAPTER: 'http',
|
||||
FLUXER_CACHE_PURGE_HTTP_ENDPOINT: 'https://purge.internal/purge',
|
||||
FLUXER_CACHE_PURGE_HTTP_TOKEN: 'purge-token',
|
||||
FLUXER_CACHE_PURGE_HTTP_TIMEOUT_MS: '5000',
|
||||
});
|
||||
expect((await loadConfig()).integrations.cache_purge).toEqual({
|
||||
adapter: 'http',
|
||||
http: {endpoint: 'https://purge.internal/purge', token: 'purge-token', timeout_ms: 5000},
|
||||
});
|
||||
});
|
||||
|
||||
test('rejects an unknown cache purge adapter', async () => {
|
||||
stubMinimalEnv({FLUXER_CACHE_PURGE_ADAPTER: 'varnish'});
|
||||
await expect(loadConfig()).rejects.toThrow('Invalid FLUXER_CACHE_PURGE_ADAPTER: varnish');
|
||||
@@ -820,63 +623,6 @@ describe('ConfigLoader', () => {
|
||||
expect((await loadConfig()).integrations.cache_purge.adapter).toBe('none');
|
||||
});
|
||||
|
||||
test('leaves the optional outbound lookups unset by default', async () => {
|
||||
stubMinimalEnv();
|
||||
|
||||
const config = await loadConfig();
|
||||
|
||||
expect(config.integrations.breached_password_check.enabled).toBeUndefined();
|
||||
});
|
||||
|
||||
test('reads the optional outbound lookup switches from the environment', async () => {
|
||||
stubMinimalEnv({
|
||||
FLUXER_BREACHED_PASSWORD_CHECK_ENABLED: 'false',
|
||||
});
|
||||
|
||||
const config = await loadConfig();
|
||||
|
||||
expect(config.integrations.breached_password_check.enabled).toBe(false);
|
||||
});
|
||||
|
||||
test('leaves Bluesky login off with no legal URLs by default', async () => {
|
||||
stubMinimalEnv();
|
||||
|
||||
const config = await loadConfig();
|
||||
|
||||
expect(config.auth.bluesky.enabled).toBe(false);
|
||||
expect(config.auth.bluesky.tos_uri).toBe('');
|
||||
expect(config.auth.bluesky.policy_uri).toBe('');
|
||||
expect(config.auth.bluesky.keys).toEqual([]);
|
||||
});
|
||||
|
||||
test('applies explicit Bluesky legal URLs from the environment', async () => {
|
||||
stubMinimalEnv({
|
||||
FLUXER_AUTH_BLUESKY_ENABLED: 'true',
|
||||
FLUXER_AUTH_BLUESKY_TOS_URI: 'https://chat.example.com/terms',
|
||||
FLUXER_AUTH_BLUESKY_POLICY_URI: 'https://chat.example.com/privacy',
|
||||
});
|
||||
|
||||
const config = await loadConfig();
|
||||
|
||||
expect(config.auth.bluesky.enabled).toBe(true);
|
||||
expect(config.auth.bluesky.tos_uri).toBe('https://chat.example.com/terms');
|
||||
expect(config.auth.bluesky.policy_uri).toBe('https://chat.example.com/privacy');
|
||||
});
|
||||
|
||||
test('reads the upload relay secret through the override table', async () => {
|
||||
const secret = Buffer.alloc(32, 9).toString('base64');
|
||||
stubMinimalEnv({FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64: secret});
|
||||
|
||||
const config = await loadConfig();
|
||||
|
||||
expect(config.services.media_proxy.upload_relay.secret_base64).toBe(secret);
|
||||
});
|
||||
|
||||
test('defaults the upload relay body limit to the media proxy ceiling', async () => {
|
||||
stubMinimalEnv();
|
||||
expect((await loadConfig()).services.media_proxy.upload_relay.max_body_bytes).toBe(524_288_000);
|
||||
});
|
||||
|
||||
test('rejects a missing upload relay secret', async () => {
|
||||
stubMinimalEnv();
|
||||
vi.stubEnv('FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64', '');
|
||||
@@ -939,11 +685,6 @@ describe('ConfigLoader', () => {
|
||||
expect(config.auth.vapid.private_key).toBe(pair.privateKey);
|
||||
});
|
||||
|
||||
test('requires a complete environment', async () => {
|
||||
vi.stubEnv('FLUXER_ENV', 'test');
|
||||
await expect(loadConfig()).rejects.toThrow();
|
||||
});
|
||||
|
||||
test('inserts the public port into the LiveKit url', async () => {
|
||||
stubMinimalEnv({FLUXER_LIVEKIT_URL: 'http://localhost/livekit'});
|
||||
|
||||
|
||||
@@ -3,61 +3,13 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
buildUrl,
|
||||
canonicalizeDomain,
|
||||
type DerivedEndpoints,
|
||||
type DomainConfig,
|
||||
deriveDomain,
|
||||
deriveEndpointsFromDomain,
|
||||
normalizePublicEndpoint,
|
||||
parsePublicOrigin,
|
||||
} from '@fluxer/config/src/EndpointDerivation';
|
||||
import {describe, expect, test} from 'vitest';
|
||||
|
||||
describe('buildUrl', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deriveDomain', () => {
|
||||
const baseConfig: DomainConfig = {
|
||||
base_domain: 'fluxer.dev',
|
||||
public_scheme: 'https',
|
||||
};
|
||||
test.each([
|
||||
'api',
|
||||
'api_client',
|
||||
'app',
|
||||
'gateway',
|
||||
'media',
|
||||
'static_cdn',
|
||||
'admin',
|
||||
'marketing',
|
||||
'invite',
|
||||
'gift',
|
||||
] as const)('uses the base domain for %s without an override', (endpoint) => {
|
||||
expect(deriveDomain(endpoint, 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);
|
||||
});
|
||||
});
|
||||
|
||||
interface EndpointScenario {
|
||||
name: string;
|
||||
config: DomainConfig;
|
||||
@@ -65,189 +17,6 @@ interface EndpointScenario {
|
||||
}
|
||||
|
||||
const endpointScenarios: Array<EndpointScenario> = [
|
||||
{
|
||||
name: 'development',
|
||||
config: {
|
||||
base_domain: 'localhost',
|
||||
public_scheme: 'http',
|
||||
public_port: 8088,
|
||||
},
|
||||
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',
|
||||
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',
|
||||
public_port: 443,
|
||||
},
|
||||
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',
|
||||
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',
|
||||
public_port: 8443,
|
||||
},
|
||||
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',
|
||||
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',
|
||||
public_port: 443,
|
||||
static_cdn_domain: 'cdn.fluxer.app',
|
||||
},
|
||||
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',
|
||||
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',
|
||||
public_port: 443,
|
||||
invite_domain: 'fluxer.gg',
|
||||
gift_domain: 'fluxer.gift',
|
||||
},
|
||||
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',
|
||||
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',
|
||||
public_port: 443,
|
||||
static_cdn_domain: 'cdn-canary.fluxer.app',
|
||||
},
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
},
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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: {
|
||||
@@ -277,107 +46,6 @@ describe('deriveEndpointsFromDomain', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizePublicEndpoint', () => {
|
||||
test('leaves a default https install untouched', () => {
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev', 'fluxer.dev', 443)).toBe('https://fluxer.dev');
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev/media', 'fluxer.dev', 443)).toBe('https://fluxer.dev/media');
|
||||
expect(normalizePublicEndpoint('wss://fluxer.dev/gateway', 'fluxer.dev', 443)).toBe('wss://fluxer.dev/gateway');
|
||||
});
|
||||
test('leaves a default http install untouched', () => {
|
||||
expect(normalizePublicEndpoint('http://fluxer.dev', 'fluxer.dev', 80)).toBe('http://fluxer.dev');
|
||||
expect(normalizePublicEndpoint('http://fluxer.dev/media', 'fluxer.dev', 80)).toBe('http://fluxer.dev/media');
|
||||
expect(normalizePublicEndpoint('ws://fluxer.dev/gateway', 'fluxer.dev', 80)).toBe('ws://fluxer.dev/gateway');
|
||||
});
|
||||
test('inserts a non-standard port', () => {
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev', 'fluxer.dev', 8443)).toBe('https://fluxer.dev:8443');
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev/media', 'fluxer.dev', 8443)).toBe(
|
||||
'https://fluxer.dev:8443/media',
|
||||
);
|
||||
expect(normalizePublicEndpoint('wss://fluxer.dev/gateway', 'fluxer.dev', 8443)).toBe(
|
||||
'wss://fluxer.dev:8443/gateway',
|
||||
);
|
||||
});
|
||||
test('judges standard ports against the url scheme, not the public scheme', () => {
|
||||
expect(normalizePublicEndpoint('http://fluxer.dev/media', 'fluxer.dev', 443)).toBe('http://fluxer.dev:443/media');
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev/media', 'fluxer.dev', 80)).toBe('https://fluxer.dev:80/media');
|
||||
});
|
||||
test('leaves a foreign host untouched', () => {
|
||||
expect(normalizePublicEndpoint('https://cdn.example.net/media', 'fluxer.dev', 8443)).toBe(
|
||||
'https://cdn.example.net/media',
|
||||
);
|
||||
expect(normalizePublicEndpoint('https://sub.fluxer.dev', 'fluxer.dev', 8443)).toBe('https://sub.fluxer.dev');
|
||||
});
|
||||
test('leaves an already ported url untouched', () => {
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev:8443/media', 'fluxer.dev', 8443)).toBe(
|
||||
'https://fluxer.dev:8443/media',
|
||||
);
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev:9000/media', 'fluxer.dev', 8443)).toBe(
|
||||
'https://fluxer.dev:9000/media',
|
||||
);
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev:443/media', 'fluxer.dev', 8443)).toBe(
|
||||
'https://fluxer.dev:443/media',
|
||||
);
|
||||
});
|
||||
test('is idempotent', () => {
|
||||
const once = normalizePublicEndpoint('https://fluxer.dev/media', 'fluxer.dev', 8443);
|
||||
expect(normalizePublicEndpoint(once, 'fluxer.dev', 8443)).toBe(once);
|
||||
});
|
||||
test('preserves path, query, fragment, trailing slash, and case', () => {
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev/Media/', 'fluxer.dev', 8443)).toBe(
|
||||
'https://fluxer.dev:8443/Media/',
|
||||
);
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev/media?a=B#Frag', 'fluxer.dev', 8443)).toBe(
|
||||
'https://fluxer.dev:8443/media?a=B#Frag',
|
||||
);
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev?a=B', 'fluxer.dev', 8443)).toBe('https://fluxer.dev:8443?a=B');
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev#Frag', 'fluxer.dev', 8443)).toBe('https://fluxer.dev:8443#Frag');
|
||||
expect(normalizePublicEndpoint('https://user:[email protected]/media', 'fluxer.dev', 8443)).toBe(
|
||||
'https://user:[email protected]:8443/media',
|
||||
);
|
||||
});
|
||||
test('matches the host case-insensitively and ignores a trailing dot', () => {
|
||||
expect(normalizePublicEndpoint('https://FLUXER.dev/media', 'fluxer.dev', 8443)).toBe(
|
||||
'https://FLUXER.dev:8443/media',
|
||||
);
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev./media', 'fluxer.dev', 8443)).toBe(
|
||||
'https://fluxer.dev.:8443/media',
|
||||
);
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev/media', 'FLUXER.dev.', 8443)).toBe(
|
||||
'https://fluxer.dev:8443/media',
|
||||
);
|
||||
});
|
||||
test('leaves unparseable and non-http values untouched', () => {
|
||||
expect(normalizePublicEndpoint('not a url', 'fluxer.dev', 8443)).toBe('not a url');
|
||||
expect(normalizePublicEndpoint('', 'fluxer.dev', 8443)).toBe('');
|
||||
expect(normalizePublicEndpoint('android:apk-key-hash:abc', 'fluxer.dev', 8443)).toBe('android:apk-key-hash:abc');
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev:/media', 'fluxer.dev', 8443)).toBe('https://fluxer.dev:/media');
|
||||
});
|
||||
test('leaves malformed authorities untouched', () => {
|
||||
expect(normalizePublicEndpoint('https:fluxer.dev/media', 'fluxer.dev', 8443)).toBe('https:fluxer.dev/media');
|
||||
expect(normalizePublicEndpoint('https:/fluxer.dev/media', 'fluxer.dev', 8443)).toBe('https:/fluxer.dev/media');
|
||||
expect(normalizePublicEndpoint('https:////fluxer.dev/media', 'fluxer.dev', 8443)).toBe(
|
||||
'https:////fluxer.dev/media',
|
||||
);
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev\\media', 'fluxer.dev', 8443)).toBe(
|
||||
'https://fluxer.dev:8443\\media',
|
||||
);
|
||||
});
|
||||
test('leaves everything untouched without a usable port or base domain', () => {
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev/media', 'fluxer.dev')).toBe('https://fluxer.dev/media');
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev/media', '', 8443)).toBe('https://fluxer.dev/media');
|
||||
expect(normalizePublicEndpoint('https://fluxer.dev/media', ' ', 8443)).toBe('https://fluxer.dev/media');
|
||||
});
|
||||
});
|
||||
|
||||
describe('canonicalizeDomain', () => {
|
||||
test('lowercases, trims and drops the root dot', () => {
|
||||
expect(canonicalizeDomain(' CHAT.Example.COM. ')).toBe('chat.example.com');
|
||||
});
|
||||
test('leaves an empty value empty', () => {
|
||||
expect(canonicalizeDomain(' ')).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parsePublicOrigin', () => {
|
||||
test('reads scheme, host and a non-standard port', () => {
|
||||
expect(parsePublicOrigin('https://chat.example.com:8443')).toEqual({
|
||||
|
||||
@@ -3,53 +3,9 @@
|
||||
import {mkdtempSync, rmSync, writeFileSync} from 'node:fs';
|
||||
import {tmpdir} from 'node:os';
|
||||
import {join} from 'node:path';
|
||||
import {
|
||||
buildNamedFluxerEnvOverrides,
|
||||
readEnvValue,
|
||||
setNestedValue,
|
||||
} from '@fluxer/config/src/config_loader/EnvironmentOverrides';
|
||||
import {buildNamedFluxerEnvOverrides, readEnvValue} from '@fluxer/config/src/config_loader/EnvironmentOverrides';
|
||||
import {afterAll, describe, expect, test} from 'vitest';
|
||||
|
||||
describe('setNestedValue', () => {
|
||||
test('sets a top-level key', () => {
|
||||
const target: Record<string, unknown> = {};
|
||||
setNestedValue(target, ['port'], 8080);
|
||||
expect(target).toEqual({port: 8080});
|
||||
});
|
||||
test('sets a nested key', () => {
|
||||
const target: Record<string, unknown> = {};
|
||||
setNestedValue(target, ['database', 'host'], 'localhost');
|
||||
expect(target).toEqual({database: {host: 'localhost'}});
|
||||
});
|
||||
test('sets a deeply nested key', () => {
|
||||
const target: Record<string, unknown> = {};
|
||||
setNestedValue(target, ['a', 'b', 'c'], 'deep');
|
||||
expect(target).toEqual({a: {b: {c: 'deep'}}});
|
||||
});
|
||||
test('does nothing for empty keys', () => {
|
||||
const target: Record<string, unknown> = {existing: true};
|
||||
setNestedValue(target, [], 'value');
|
||||
expect(target).toEqual({existing: true});
|
||||
});
|
||||
test('overwrites non-object intermediate values', () => {
|
||||
const target: Record<string, unknown> = {a: 'string'};
|
||||
setNestedValue(target, ['a', 'b'], 'nested');
|
||||
expect(target).toEqual({a: {b: 'nested'}});
|
||||
});
|
||||
test('creates arrays for numeric path keys', () => {
|
||||
const target: Record<string, unknown> = {};
|
||||
setNestedValue(target, ['auth', 'bluesky', 'keys', 0, 'kid'], 'key-1');
|
||||
setNestedValue(target, ['auth', 'bluesky', 'keys', 0, 'private_key_path'], '/etc/fluxer/keys/key.pem');
|
||||
expect(target).toEqual({
|
||||
auth: {
|
||||
bluesky: {
|
||||
keys: [{kid: 'key-1', private_key_path: '/etc/fluxer/keys/key.pem'}],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildNamedFluxerEnvOverrides', () => {
|
||||
test('builds canonical split env overrides', () => {
|
||||
const overrides = buildNamedFluxerEnvOverrides({
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
ASSET_FORMAT_POLICY,
|
||||
formatAssetUploadExtensions,
|
||||
formatKnownAnimatedAssetExtensions,
|
||||
getAcceptString,
|
||||
getUploadExtensions,
|
||||
isExtensionAllowed,
|
||||
} from '@fluxer/constants/src/AssetFormatPolicy';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
describe('AssetFormatPolicy', () => {
|
||||
describe('ASSET_FORMAT_POLICY', () => {
|
||||
it('exposes an entry for every documented asset kind', () => {
|
||||
expect(Object.keys(ASSET_FORMAT_POLICY).sort()).toEqual(
|
||||
['attachment', 'avatar', 'banner', 'embed_splash', 'emoji', 'guild_icon', 'splash', 'sticker'].sort(),
|
||||
);
|
||||
});
|
||||
it('keeps animated-only assets out of static-only kinds', () => {
|
||||
for (const kind of ['splash', 'embed_splash'] as const) {
|
||||
const entry = ASSET_FORMAT_POLICY[kind];
|
||||
expect(entry.animated).toBe('never');
|
||||
expect(entry.upload).not.toContain('gif');
|
||||
expect(entry.upload).not.toContain('apng');
|
||||
}
|
||||
});
|
||||
it('includes HEIC/JXL/AVIF on avatar uploads (iPhone + modern web)', () => {
|
||||
expect(ASSET_FORMAT_POLICY.avatar.upload).toEqual(
|
||||
expect.arrayContaining(['heic', 'heif', 'jxl', 'avif', 'apng']),
|
||||
);
|
||||
});
|
||||
it('limits sticker uploads to the historical sticker set plus jpeg and svg', () => {
|
||||
expect([...ASSET_FORMAT_POLICY.sticker.upload].sort()).toEqual([
|
||||
'apng',
|
||||
'avif',
|
||||
'gif',
|
||||
'jpeg',
|
||||
'png',
|
||||
'svg',
|
||||
'webp',
|
||||
]);
|
||||
});
|
||||
it('accepts svg uploads on every image asset kind', () => {
|
||||
for (const kind of [
|
||||
'avatar',
|
||||
'guild_icon',
|
||||
'banner',
|
||||
'splash',
|
||||
'embed_splash',
|
||||
'emoji',
|
||||
'sticker',
|
||||
'attachment',
|
||||
] as const) {
|
||||
expect(ASSET_FORMAT_POLICY[kind].upload).toContain('svg');
|
||||
expect(ASSET_FORMAT_POLICY[kind].mimes.svg).toBe('image/svg+xml');
|
||||
}
|
||||
});
|
||||
it('has a mime entry for every accepted extension', () => {
|
||||
for (const entry of Object.values(ASSET_FORMAT_POLICY)) {
|
||||
for (const ext of entry.upload) {
|
||||
expect(entry.mimes[ext]).toMatch(/^image\//);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('getAcceptString', () => {
|
||||
it('returns extensions and MIMEs joined by commas', () => {
|
||||
const accept = getAcceptString('avatar');
|
||||
expect(accept).toContain('.png');
|
||||
expect(accept).toContain('image/png');
|
||||
expect(accept).toContain('.heic');
|
||||
expect(accept).toContain('image/heic');
|
||||
expect(accept).toContain('.jxl');
|
||||
});
|
||||
it('does not include image/* wildcard (we want strict negotiation)', () => {
|
||||
expect(getAcceptString('emoji')).not.toContain('image/*');
|
||||
});
|
||||
it('can filter extension-only animated formats when animation is unavailable', () => {
|
||||
const accept = getAcceptString('guild_icon', {animatedAllowed: false});
|
||||
expect(accept).not.toContain('.gif');
|
||||
expect(accept).not.toContain('image/gif');
|
||||
expect(accept).not.toContain('.apng');
|
||||
expect(accept).not.toContain('image/apng');
|
||||
expect(accept).toContain('.svg');
|
||||
expect(accept).toContain('image/svg+xml');
|
||||
expect(accept).toContain('.avif');
|
||||
expect(accept).toContain('image/avif');
|
||||
});
|
||||
});
|
||||
describe('getUploadExtensions', () => {
|
||||
it('returns the full backend upload policy by default', () => {
|
||||
expect(getUploadExtensions('avatar')).toEqual([
|
||||
'png',
|
||||
'jpeg',
|
||||
'webp',
|
||||
'gif',
|
||||
'apng',
|
||||
'avif',
|
||||
'heic',
|
||||
'heif',
|
||||
'jxl',
|
||||
'svg',
|
||||
]);
|
||||
});
|
||||
it('keeps static-capable expanded formats when filtering animation-only extensions', () => {
|
||||
expect(getUploadExtensions('avatar', {animatedAllowed: false})).toEqual([
|
||||
'png',
|
||||
'jpeg',
|
||||
'webp',
|
||||
'avif',
|
||||
'heic',
|
||||
'heif',
|
||||
'jxl',
|
||||
'svg',
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('formatAssetUploadExtensions', () => {
|
||||
it('formats user-facing labels from the upload policy', () => {
|
||||
expect(formatAssetUploadExtensions('avatar')).toBe('PNG, JPEG, WebP, GIF, APNG, AVIF, HEIC, HEIF, JXL, SVG');
|
||||
});
|
||||
it('supports extension-style labels for API validation details', () => {
|
||||
expect(formatAssetUploadExtensions('avatar', {labelStyle: 'extension'})).toBe(
|
||||
'png, jpeg, webp, gif, apng, avif, heic, heif, jxl, svg',
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('formatKnownAnimatedAssetExtensions', () => {
|
||||
it('lists animated formats that are accepted as animated assets', () => {
|
||||
expect(formatKnownAnimatedAssetExtensions('avatar')).toBe('GIF, APNG, WebP');
|
||||
});
|
||||
});
|
||||
describe('isExtensionAllowed', () => {
|
||||
it('accepts both raw and dot-prefixed extensions', () => {
|
||||
expect(isExtensionAllowed('avatar', 'png')).toBe(true);
|
||||
expect(isExtensionAllowed('avatar', '.png')).toBe(true);
|
||||
expect(isExtensionAllowed('avatar', 'PNG')).toBe(true);
|
||||
expect(isExtensionAllowed('avatar', 'jpg')).toBe(true);
|
||||
});
|
||||
it('rejects extensions not in the upload list', () => {
|
||||
expect(isExtensionAllowed('splash', 'gif')).toBe(false);
|
||||
expect(isExtensionAllowed('emoji', 'mp4' as never)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,46 +1,11 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
ANNOUNCEMENT_CONVERTIBLE_CHANNEL_TYPES,
|
||||
CHANNEL_FOLLOW_TARGET_TYPES,
|
||||
ChannelTypes,
|
||||
GUILD_TEXT_BASED_CHANNEL_TYPES,
|
||||
isMessageTypeDeletable,
|
||||
MessageFlags,
|
||||
MessageTypes,
|
||||
SENDABLE_MESSAGE_FLAGS,
|
||||
TEXT_BASED_CHANNEL_TYPES,
|
||||
WebhookTypes,
|
||||
} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {MessageFlags, SENDABLE_MESSAGE_FLAGS} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
describe('announcement channel constants', () => {
|
||||
it('treats announcement channels as guild text-based channels', () => {
|
||||
expect(GUILD_TEXT_BASED_CHANNEL_TYPES.has(ChannelTypes.GUILD_ANNOUNCEMENT)).toBe(true);
|
||||
expect(TEXT_BASED_CHANNEL_TYPES.has(ChannelTypes.GUILD_ANNOUNCEMENT)).toBe(true);
|
||||
});
|
||||
|
||||
it('converts only between text and announcement channels', () => {
|
||||
expect([...ANNOUNCEMENT_CONVERTIBLE_CHANNEL_TYPES].sort()).toEqual([
|
||||
ChannelTypes.GUILD_TEXT,
|
||||
ChannelTypes.GUILD_ANNOUNCEMENT,
|
||||
]);
|
||||
});
|
||||
|
||||
it('lets follows post only into text channels', () => {
|
||||
expect([...CHANNEL_FOLLOW_TARGET_TYPES]).toEqual([ChannelTypes.GUILD_TEXT]);
|
||||
});
|
||||
|
||||
it('lets members delete channel follow system messages', () => {
|
||||
expect(isMessageTypeDeletable(MessageTypes.CHANNEL_FOLLOW_ADD)).toBe(true);
|
||||
});
|
||||
|
||||
it('keeps the crosspost flags out of the sendable flags', () => {
|
||||
const crosspostFlags = MessageFlags.CROSSPOSTED | MessageFlags.IS_CROSSPOST | MessageFlags.SOURCE_MESSAGE_DELETED;
|
||||
expect(crosspostFlags & SENDABLE_MESSAGE_FLAGS).toBe(0);
|
||||
});
|
||||
|
||||
it('names both webhook types', () => {
|
||||
expect(WebhookTypes).toEqual({INCOMING: 1, CHANNEL_FOLLOWER: 2});
|
||||
});
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
entranceSoundExtensionFromFormat,
|
||||
entranceSoundExtensionFromMime,
|
||||
} from '@fluxer/constants/src/EntranceSoundConstants';
|
||||
import {describe, expect, test} from 'vitest';
|
||||
|
||||
describe('entrance sound format detection', () => {
|
||||
test('accepts wav metadata produced by the in-app trimmer', () => {
|
||||
expect(entranceSoundExtensionFromFormat('wav')).toBe('wav');
|
||||
expect(entranceSoundExtensionFromMime('audio/wav')).toBe('wav');
|
||||
});
|
||||
|
||||
test('accepts common ffprobe compound format names', () => {
|
||||
expect(entranceSoundExtensionFromFormat('mp3,mp2,mp1')).toBe('mp3');
|
||||
expect(entranceSoundExtensionFromFormat('mov,mp4,m4a,3gp,3g2,mj2')).toBe('m4a');
|
||||
});
|
||||
});
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
formatRecoveryKeyInput,
|
||||
generateRecoveryKey,
|
||||
normalizeRecoveryKey,
|
||||
RECOVERY_KEY_FORMATTED_LENGTH,
|
||||
RECOVERY_KEY_LENGTH,
|
||||
} from '@fluxer/constants/src/RecoveryKeyUtils';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
@@ -34,7 +33,7 @@ describe('generateRecoveryKey', () => {
|
||||
it('returns the raw key and its display form', () => {
|
||||
const {key, formatted} = generateRecoveryKey(new Uint8Array(20).fill(0xa5));
|
||||
expect(key).toHaveLength(RECOVERY_KEY_LENGTH);
|
||||
expect(formatted).toHaveLength(RECOVERY_KEY_FORMATTED_LENGTH);
|
||||
expect(formatted).toHaveLength(39);
|
||||
expect(formatted.split('-')).toHaveLength(8);
|
||||
expect(formatted.replaceAll('-', '')).toBe(key);
|
||||
expect(normalizeRecoveryKey(formatted)).toBe(key);
|
||||
|
||||
@@ -5,7 +5,6 @@ export const RECOVERY_KEY_BYTE_LENGTH = 20;
|
||||
export const RECOVERY_KEY_LENGTH = 32;
|
||||
const RECOVERY_KEY_GROUP_SIZE = 4;
|
||||
export const RECOVERY_KEY_SEPARATOR = '-';
|
||||
export const RECOVERY_KEY_FORMATTED_LENGTH = RECOVERY_KEY_LENGTH + RECOVERY_KEY_LENGTH / RECOVERY_KEY_GROUP_SIZE - 1;
|
||||
|
||||
const RECOVERY_KEY_ALIASES: Readonly<Record<string, string>> = {
|
||||
O: '0',
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
canViewThread,
|
||||
DEFAULT_THREAD_PERMISSIONS,
|
||||
isThreadModerator,
|
||||
messageSendPermissionFor,
|
||||
THREAD_AWARE_ALL_PERMISSIONS,
|
||||
THREAD_PERMISSIONS,
|
||||
type ThreadActorContext,
|
||||
@@ -156,15 +155,4 @@ describe('thread permission bits', () => {
|
||||
Permissions.SEND_MESSAGES,
|
||||
);
|
||||
});
|
||||
|
||||
it('picks the send bit by channel type', () => {
|
||||
expect(messageSendPermissionFor(ChannelTypes.ANNOUNCEMENT_THREAD)).toBe(
|
||||
ThreadPermissionFlags.SEND_MESSAGES_IN_THREADS,
|
||||
);
|
||||
expect(messageSendPermissionFor(ChannelTypes.PUBLIC_THREAD)).toBe(ThreadPermissionFlags.SEND_MESSAGES_IN_THREADS);
|
||||
expect(messageSendPermissionFor(ChannelTypes.PRIVATE_THREAD)).toBe(ThreadPermissionFlags.SEND_MESSAGES_IN_THREADS);
|
||||
expect(messageSendPermissionFor(ChannelTypes.GUILD_TEXT)).toBe(Permissions.SEND_MESSAGES);
|
||||
expect(messageSendPermissionFor(ChannelTypes.GUILD_ANNOUNCEMENT)).toBe(Permissions.SEND_MESSAGES);
|
||||
expect(messageSendPermissionFor(ChannelTypes.GUILD_FORUM)).toBe(Permissions.SEND_MESSAGES);
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
|
||||
import {ALL_PERMISSIONS, ChannelTypes, Permissions} from '@fluxer/constants/src/ChannelConstants';
|
||||
import {THREAD_CHANNEL_TYPES} from '@fluxer/constants/src/ThreadConstants';
|
||||
|
||||
export const ThreadPermissionFlags = {
|
||||
MANAGE_THREADS: 1n << 34n,
|
||||
@@ -89,10 +88,6 @@ export function threadViewPermissions(parentPerms: bigint): bigint {
|
||||
: withoutSend;
|
||||
}
|
||||
|
||||
export function messageSendPermissionFor(type: number): bigint {
|
||||
return THREAD_CHANNEL_TYPES.has(type) ? ThreadPermissionFlags.SEND_MESSAGES_IN_THREADS : Permissions.SEND_MESSAGES;
|
||||
}
|
||||
|
||||
export function canViewThread(ctx: ThreadActorContext): ThreadDenial | null {
|
||||
const perms = withImplicitThreadBits(ctx.permissions);
|
||||
if (!ctx.isOwner && !has(perms, Permissions.VIEW_CHANNEL)) return APIErrorCodes.MISSING_ACCESS;
|
||||
|
||||
@@ -12,9 +12,7 @@ export const ValidationErrorCodes = {
|
||||
ANIMATED_GUILD_BANNER_REQUIRES_FEATURE: 'ANIMATED_GUILD_BANNER_REQUIRES_FEATURE',
|
||||
AT_LEAST_ONE_ENTRY_IS_REQUIRED: 'AT_LEAST_ONE_ENTRY_IS_REQUIRED',
|
||||
AT_LEAST_ONE_RECIPIENT_REQUIRED: 'AT_LEAST_ONE_RECIPIENT_REQUIRED',
|
||||
ATTACHMENT_FIELDS_REQUIRED: 'ATTACHMENT_FIELDS_REQUIRED',
|
||||
ATTACHMENT_ID_NOT_FOUND_IN_MESSAGE: 'ATTACHMENT_ID_NOT_FOUND_IN_MESSAGE',
|
||||
ATTACHMENT_IDS_MUST_BE_VALID_INTEGERS: 'ATTACHMENT_IDS_MUST_BE_VALID_INTEGERS',
|
||||
ATTACHMENT_MUST_BE_IMAGE: 'ATTACHMENT_MUST_BE_IMAGE',
|
||||
ATTACHMENTS_NOT_ALLOWED_FOR_MESSAGE: 'ATTACHMENTS_NOT_ALLOWED_FOR_MESSAGE',
|
||||
AVATAR_CHANGED_TOO_MANY_TIMES: 'AVATAR_CHANGED_TOO_MANY_TIMES',
|
||||
@@ -22,7 +20,6 @@ export const ValidationErrorCodes = {
|
||||
BANNERS_REQUIRE_PREMIUM: 'BANNERS_REQUIRE_PREMIUM',
|
||||
BASE64_LENGTH_INVALID: 'BASE64_LENGTH_INVALID',
|
||||
BIO_CHANGED_TOO_MANY_TIMES: 'BIO_CHANGED_TOO_MANY_TIMES',
|
||||
BUCKET_IS_REQUIRED: 'BUCKET_IS_REQUIRED',
|
||||
CANNOT_ADD_YOURSELF_TO_GROUP_DM: 'CANNOT_ADD_YOURSELF_TO_GROUP_DM',
|
||||
CANNOT_DELETE_MORE_THAN_100_MESSAGES: 'CANNOT_DELETE_MORE_THAN_100_MESSAGES',
|
||||
CANNOT_DM_YOURSELF: 'CANNOT_DM_YOURSELF',
|
||||
@@ -183,9 +180,7 @@ export const ValidationErrorCodes = {
|
||||
RECIPIENT_IDS_MUST_BE_STRINGS: 'RECIPIENT_IDS_MUST_BE_STRINGS',
|
||||
RECIPIENT_IDS_MUST_BE_VALID_SNOWFLAKES: 'RECIPIENT_IDS_MUST_BE_VALID_SNOWFLAKES',
|
||||
REFERENCED_ATTACHMENT_NOT_FOUND: 'REFERENCED_ATTACHMENT_NOT_FOUND',
|
||||
ROWS_IS_REQUIRED: 'ROWS_IS_REQUIRED',
|
||||
SESSION_TIMEOUT: 'SESSION_TIMEOUT',
|
||||
SIZE_BYTES_MUST_BE_VALID_INTEGER: 'SIZE_BYTES_MUST_BE_VALID_INTEGER',
|
||||
SSO_IDENTITY_MISMATCH: 'SSO_IDENTITY_MISMATCH',
|
||||
SSO_MISCONFIGURED: 'SSO_MISCONFIGURED',
|
||||
SSO_PROVIDER_DID_NOT_RETURN_EMAIL: 'SSO_PROVIDER_DID_NOT_RETURN_EMAIL',
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
export const NativeGatewayTransportMode = Object.freeze({
|
||||
GATEWAY: 'gateway',
|
||||
VOICE: 'voice',
|
||||
} as const);
|
||||
|
||||
export type NativeGatewayTransportMode = (typeof NativeGatewayTransportMode)[keyof typeof NativeGatewayTransportMode];
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
export interface DesktopHTTPTransportRequest {
|
||||
readonly url: string;
|
||||
readonly method: string;
|
||||
readonly headers: Readonly<Record<string, string>>;
|
||||
readonly body: string | null;
|
||||
readonly timeoutMs: number;
|
||||
}
|
||||
|
||||
export interface DesktopHTTPTransportResponse {
|
||||
readonly ok: boolean;
|
||||
readonly status: number;
|
||||
readonly statusText: string;
|
||||
readonly headers: Readonly<Record<string, string>>;
|
||||
readonly body: string | null;
|
||||
}
|
||||
|
||||
export interface DesktopHTTPTransportAPI {
|
||||
fetch(request: DesktopHTTPTransportRequest): Promise<DesktopHTTPTransportResponse>;
|
||||
}
|
||||
@@ -57,24 +57,6 @@ export function desktopLegacyReplantMarker(replantedAt: number): string {
|
||||
return JSON.stringify({version: DESKTOP_LEGACY_REPLANT_MARKER_VERSION, replantedAt});
|
||||
}
|
||||
|
||||
export function isDesktopLegacyReplantMarker(value: string | null): boolean {
|
||||
if (value === null) return false;
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(value);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return false;
|
||||
const record = parsed as {version?: unknown; replantedAt?: unknown};
|
||||
return (
|
||||
record.version === DESKTOP_LEGACY_REPLANT_MARKER_VERSION &&
|
||||
typeof record.replantedAt === 'number' &&
|
||||
Number.isFinite(record.replantedAt) &&
|
||||
record.replantedAt > 0
|
||||
);
|
||||
}
|
||||
|
||||
export interface DesktopLegacyHarvestRecord {
|
||||
readonly key: unknown | null;
|
||||
readonly value: unknown;
|
||||
|
||||
@@ -16,11 +16,7 @@ export const DESKTOP_UPDATE_EVENTS = Object.freeze({
|
||||
|
||||
const DESKTOP_MODULE_NAME_PATTERN = /^[a-z][a-z0-9_]{0,63}$/u;
|
||||
export const DESKTOP_RENDERER_MODULE_NAME = 'fluxer_renderer';
|
||||
export const DESKTOP_TWEMOJI_MODULE_NAME = 'fluxer_twemoji';
|
||||
export const DESKTOP_DEEP_FILTER_MODULE_NAME = 'fluxer_deepfilter';
|
||||
export const DESKTOP_CAMERA_EFFECTS_MODULE_NAME = 'fluxer_camera_effects';
|
||||
export const DESKTOP_EMOJI_SPRITES_MODULE_NAME = 'fluxer_emoji_sprites';
|
||||
export const DESKTOP_EXTRAS_MODULE_NAME = 'fluxer_extras';
|
||||
export const DESKTOP_SOURCEMAP_MODULE_NAME = 'fluxer_sourcemaps';
|
||||
|
||||
export const DESKTOP_FONT_MODULE_NAMES = Object.freeze({
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
export * from '@fluxer/desktop_ipc/src/AccountContract';
|
||||
export * from '@fluxer/desktop_ipc/src/BrowserHandoffContract';
|
||||
export * from '@fluxer/desktop_ipc/src/CapabilityManifest';
|
||||
export * from '@fluxer/desktop_ipc/src/GatewayTransportContract';
|
||||
export * from '@fluxer/desktop_ipc/src/HTTPTransportContract';
|
||||
export * from '@fluxer/desktop_ipc/src/KnownInstanceContract';
|
||||
export * from '@fluxer/desktop_ipc/src/LastRouteContract';
|
||||
export * from '@fluxer/desktop_ipc/src/LegacyHarvestContract';
|
||||
export * from '@fluxer/desktop_ipc/src/LocalAppRouteContract';
|
||||
export * from '@fluxer/desktop_ipc/src/LocalAppRuntimeContract';
|
||||
export * from '@fluxer/desktop_ipc/src/ModuleContract';
|
||||
export * from '@fluxer/desktop_ipc/src/StorageContract';
|
||||
@@ -1,60 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {HttpStatus} from '@fluxer/constants/src/HttpConstants';
|
||||
import {getApiErrorCodeForStatus} from '@fluxer/errors/src/error_handling/ErrorIntrospection';
|
||||
import {createJsonErrorResponse, createXmlErrorResponse} from '@fluxer/errors/src/error_handling/ErrorResponse';
|
||||
import {FluxerError} from '@fluxer/errors/src/FluxerError';
|
||||
import type {Context, ErrorHandler} from 'hono';
|
||||
import {HTTPException} from 'hono/http-exception';
|
||||
|
||||
type ResponseFormat = 'json' | 'xml';
|
||||
|
||||
interface ErrorHandlerOptions {
|
||||
logError?: (error: Error, context: Context) => void;
|
||||
includeStack?: boolean;
|
||||
responseFormat?: ResponseFormat;
|
||||
customHandler?: (error: Error, context: Context) => Response | Promise<Response> | undefined;
|
||||
}
|
||||
|
||||
export function createErrorHandler(options: ErrorHandlerOptions = {}): ErrorHandler {
|
||||
const {logError, includeStack = false, responseFormat = 'json', customHandler} = options;
|
||||
return async (error: Error, c: Context): Promise<Response> => {
|
||||
if (logError) {
|
||||
logError(error, c);
|
||||
}
|
||||
if (customHandler) {
|
||||
const customResponse = await customHandler(error, c);
|
||||
if (customResponse) {
|
||||
return customResponse;
|
||||
}
|
||||
}
|
||||
if (error instanceof FluxerError) {
|
||||
return error.getResponse();
|
||||
}
|
||||
if (error instanceof HTTPException) {
|
||||
const status = error.status;
|
||||
const code = getApiErrorCodeForStatus(status);
|
||||
const message = error.message || 'An error occurred';
|
||||
if (responseFormat === 'xml') {
|
||||
return createXmlErrorResponse(status, code, message);
|
||||
}
|
||||
return createJsonErrorResponse({
|
||||
status,
|
||||
code,
|
||||
message,
|
||||
data: includeStack ? {stack: error.stack} : undefined,
|
||||
});
|
||||
}
|
||||
const status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
const message = includeStack ? error.message : 'Something went wrong. Please try again later.';
|
||||
if (responseFormat === 'xml') {
|
||||
return createXmlErrorResponse(status, 'INTERNAL_SERVER_ERROR', message);
|
||||
}
|
||||
return createJsonErrorResponse({
|
||||
status,
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message,
|
||||
data: includeStack ? {stack: error.stack} : undefined,
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -12,78 +12,6 @@ interface HttpErrorOptions {
|
||||
cause?: Error;
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
this.name = 'BadRequestError';
|
||||
}
|
||||
}
|
||||
|
||||
export class UnauthorizedError extends FluxerError {
|
||||
constructor(options: HttpErrorOptions = {}) {
|
||||
super({
|
||||
...options,
|
||||
code: options.code ?? APIErrorCodes.UNAUTHORIZED,
|
||||
message: options.message ?? 'Unauthorized',
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
});
|
||||
this.name = 'UnauthorizedError';
|
||||
}
|
||||
}
|
||||
|
||||
export class ForbiddenError extends FluxerError {
|
||||
constructor(options: HttpErrorOptions = {}) {
|
||||
super({
|
||||
...options,
|
||||
code: options.code ?? APIErrorCodes.FORBIDDEN,
|
||||
message: options.message ?? 'Forbidden',
|
||||
status: HttpStatus.FORBIDDEN,
|
||||
});
|
||||
this.name = 'ForbiddenError';
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
this.name = 'NotFoundError';
|
||||
}
|
||||
}
|
||||
|
||||
export class ConflictError extends FluxerError {
|
||||
constructor(options: HttpErrorOptions = {}) {
|
||||
super({
|
||||
...options,
|
||||
code: options.code ?? APIErrorCodes.CONFLICT,
|
||||
message: options.message ?? 'Conflict',
|
||||
status: HttpStatus.CONFLICT,
|
||||
});
|
||||
this.name = 'ConflictError';
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
this.name = 'InternalServerError';
|
||||
}
|
||||
}
|
||||
|
||||
export class ServiceUnavailableError extends FluxerError {
|
||||
constructor(options: HttpErrorOptions = {}) {
|
||||
super({
|
||||
@@ -95,27 +23,3 @@ export class ServiceUnavailableError extends FluxerError {
|
||||
this.name = 'ServiceUnavailableError';
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
this.name = 'BadGatewayError';
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
this.name = 'GatewayTimeoutError';
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
|
||||
import {HttpStatus} from '@fluxer/constants/src/HttpConstants';
|
||||
import {AnnouncementChannelRequiredError} from '@fluxer/errors/src/domains/channel/AnnouncementChannelRequiredError';
|
||||
import {ChannelAlreadyFollowedError} from '@fluxer/errors/src/domains/channel/ChannelAlreadyFollowedError';
|
||||
import {ChannelHasFollowedChannelsError} from '@fluxer/errors/src/domains/channel/ChannelHasFollowedChannelsError';
|
||||
import {ChannelHasThreadsError} from '@fluxer/errors/src/domains/channel/ChannelHasThreadsError';
|
||||
import {ChannelTypeConversionNotSupportedError} from '@fluxer/errors/src/domains/channel/ChannelTypeConversionNotSupportedError';
|
||||
import {FollowTargetContentWarningRequiredError} from '@fluxer/errors/src/domains/channel/FollowTargetContentWarningRequiredError';
|
||||
import {FollowTargetNotAgeRestrictedError} from '@fluxer/errors/src/domains/channel/FollowTargetNotAgeRestrictedError';
|
||||
import {InvalidFollowTargetChannelError} from '@fluxer/errors/src/domains/channel/InvalidFollowTargetChannelError';
|
||||
import {MessageAlreadyCrosspostedError} from '@fluxer/errors/src/domains/channel/MessageAlreadyCrosspostedError';
|
||||
import {MessageNotCrosspostableError} from '@fluxer/errors/src/domains/channel/MessageNotCrosspostableError';
|
||||
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
|
||||
import {getErrorMessageUnsafe} from '@fluxer/errors/src/i18n/ErrorI18n';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
describe.each([
|
||||
[APIErrorCodes.ANNOUNCEMENT_CHANNEL_REQUIRED, AnnouncementChannelRequiredError],
|
||||
[APIErrorCodes.CHANNEL_ALREADY_FOLLOWED, ChannelAlreadyFollowedError],
|
||||
[APIErrorCodes.CHANNEL_HAS_FOLLOWED_CHANNELS, ChannelHasFollowedChannelsError],
|
||||
[APIErrorCodes.CHANNEL_HAS_THREADS, ChannelHasThreadsError],
|
||||
[APIErrorCodes.CHANNEL_TYPE_CONVERSION_NOT_SUPPORTED, ChannelTypeConversionNotSupportedError],
|
||||
[APIErrorCodes.FOLLOW_TARGET_CONTENT_WARNING_REQUIRED, FollowTargetContentWarningRequiredError],
|
||||
[APIErrorCodes.FOLLOW_TARGET_NOT_AGE_RESTRICTED, FollowTargetNotAgeRestrictedError],
|
||||
[APIErrorCodes.INVALID_FOLLOW_TARGET_CHANNEL, InvalidFollowTargetChannelError],
|
||||
[APIErrorCodes.MESSAGE_ALREADY_CROSSPOSTED, MessageAlreadyCrosspostedError],
|
||||
[APIErrorCodes.MESSAGE_NOT_CROSSPOSTABLE, MessageNotCrosspostableError],
|
||||
] as const)('%s', (code, ErrorClass) => {
|
||||
it('is a 400 with its own code', async () => {
|
||||
const error = new ErrorClass();
|
||||
expect(error).toBeInstanceOf(BadRequestError);
|
||||
expect(error).toMatchObject({status: HttpStatus.BAD_REQUEST, code});
|
||||
expect(await error.getResponse().json()).toEqual({code, message: code});
|
||||
});
|
||||
|
||||
it('has an English message', () => {
|
||||
expect(getErrorMessageUnsafe(code, 'en-US')).not.toBe(code);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([APIErrorCodes.MESSAGE_CROSSPOST_RATE_LIMITED, APIErrorCodes.PUBLISHED_MESSAGE_EDIT_RATE_LIMITED])(
|
||||
'%s',
|
||||
(code) => {
|
||||
it('has an English message', () => {
|
||||
expect(getErrorMessageUnsafe(code, 'en-US')).not.toBe(code);
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -1,295 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
|
||||
import {HttpStatus} from '@fluxer/constants/src/HttpConstants';
|
||||
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
|
||||
import {AccountIdentityLockedError} from '@fluxer/errors/src/domains/auth/AccountIdentityLockedError';
|
||||
import {EmailUnavailableOnInstanceError} from '@fluxer/errors/src/domains/auth/EmailUnavailableOnInstanceError';
|
||||
import {UsernameSignInOnlyError} from '@fluxer/errors/src/domains/auth/UsernameSignInOnlyError';
|
||||
import {MaxActiveThreadsError} from '@fluxer/errors/src/domains/channel/MaxActiveThreadsError';
|
||||
import {SearchIndexNotReadyError} from '@fluxer/errors/src/domains/channel/SearchIndexNotReadyError';
|
||||
import {ThreadArchivedError} from '@fluxer/errors/src/domains/channel/ThreadArchivedError';
|
||||
import {ThreadLockedError} from '@fluxer/errors/src/domains/channel/ThreadLockedError';
|
||||
import {UnknownChannelError} from '@fluxer/errors/src/domains/channel/UnknownChannelError';
|
||||
import {UnknownMessageError} from '@fluxer/errors/src/domains/channel/UnknownMessageError';
|
||||
import {UnknownThreadMemberError} from '@fluxer/errors/src/domains/channel/UnknownThreadMemberError';
|
||||
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
|
||||
import {ConflictError} from '@fluxer/errors/src/domains/core/ConflictError';
|
||||
import {ForbiddenError} from '@fluxer/errors/src/domains/core/ForbiddenError';
|
||||
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
|
||||
import {InternalServerError} from '@fluxer/errors/src/domains/core/InternalServerError';
|
||||
import {NotFoundError} from '@fluxer/errors/src/domains/core/NotFoundError';
|
||||
import {ServiceUnavailableError} from '@fluxer/errors/src/domains/core/ServiceUnavailableError';
|
||||
import {IpBannedError} from '@fluxer/errors/src/domains/moderation/IpBannedError';
|
||||
import {PremiumPurchaseBlockedError} from '@fluxer/errors/src/domains/payment/PremiumPurchaseBlockedError';
|
||||
import {StoreBillingUnavailableError} from '@fluxer/errors/src/domains/payment/StoreBillingUnavailableError';
|
||||
import {StoreNotificationUnauthorizedError} from '@fluxer/errors/src/domains/payment/StoreNotificationUnauthorizedError';
|
||||
import {StorePurchaseInvalidError} from '@fluxer/errors/src/domains/payment/StorePurchaseInvalidError';
|
||||
import {StorePurchaseOwnedByOtherAccountError} from '@fluxer/errors/src/domains/payment/StorePurchaseOwnedByOtherAccountError';
|
||||
import {StorePurchaseSandboxNotEntitledError} from '@fluxer/errors/src/domains/payment/StorePurchaseSandboxNotEntitledError';
|
||||
import {UnknownStorePurchaseError} from '@fluxer/errors/src/domains/payment/UnknownStorePurchaseError';
|
||||
import {AccountLimitedError} from '@fluxer/errors/src/domains/user/AccountLimitedError';
|
||||
import {FluxerError} from '@fluxer/errors/src/FluxerError';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
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();
|
||||
|
||||
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},
|
||||
});
|
||||
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.ACCOUNT_LIMITED, AccountLimitedError, ForbiddenError, HttpStatus.FORBIDDEN],
|
||||
[APIErrorCodes.ACCOUNT_IDENTITY_LOCKED, AccountIdentityLockedError, ConflictError, HttpStatus.CONFLICT],
|
||||
[
|
||||
APIErrorCodes.EMAIL_UNAVAILABLE_ON_INSTANCE,
|
||||
EmailUnavailableOnInstanceError,
|
||||
BadRequestError,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
],
|
||||
[APIErrorCodes.USERNAME_SIGN_IN_ONLY, UsernameSignInOnlyError, BadRequestError, HttpStatus.BAD_REQUEST],
|
||||
[APIErrorCodes.UNKNOWN_CHANNEL, UnknownChannelError, NotFoundError, HttpStatus.NOT_FOUND],
|
||||
[APIErrorCodes.UNKNOWN_MESSAGE, UnknownMessageError, NotFoundError, HttpStatus.NOT_FOUND],
|
||||
[APIErrorCodes.STORE_PURCHASE_INVALID, StorePurchaseInvalidError, BadRequestError, HttpStatus.BAD_REQUEST],
|
||||
[
|
||||
APIErrorCodes.STORE_PURCHASE_OWNED_BY_OTHER_ACCOUNT,
|
||||
StorePurchaseOwnedByOtherAccountError,
|
||||
ForbiddenError,
|
||||
HttpStatus.FORBIDDEN,
|
||||
],
|
||||
[
|
||||
APIErrorCodes.STORE_PURCHASE_SANDBOX_NOT_ENTITLED,
|
||||
StorePurchaseSandboxNotEntitledError,
|
||||
ForbiddenError,
|
||||
HttpStatus.FORBIDDEN,
|
||||
],
|
||||
[
|
||||
APIErrorCodes.STORE_BILLING_UNAVAILABLE,
|
||||
StoreBillingUnavailableError,
|
||||
ServiceUnavailableError,
|
||||
HttpStatus.SERVICE_UNAVAILABLE,
|
||||
],
|
||||
[APIErrorCodes.UNKNOWN_STORE_PURCHASE, UnknownStorePurchaseError, NotFoundError, HttpStatus.NOT_FOUND],
|
||||
[
|
||||
APIErrorCodes.STORE_NOTIFICATION_UNAUTHORIZED,
|
||||
StoreNotificationUnauthorizedError,
|
||||
FluxerError,
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
],
|
||||
[APIErrorCodes.THREAD_ARCHIVED, ThreadArchivedError, BadRequestError, HttpStatus.BAD_REQUEST],
|
||||
[APIErrorCodes.THREAD_LOCKED, ThreadLockedError, BadRequestError, HttpStatus.BAD_REQUEST],
|
||||
[APIErrorCodes.UNKNOWN_THREAD_MEMBER, UnknownThreadMemberError, 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('thread limit and index errors', () => {
|
||||
it('reports the thread limit in data and message variables', async () => {
|
||||
const error = new MaxActiveThreadsError(1000);
|
||||
expect(error.status).toBe(HttpStatus.BAD_REQUEST);
|
||||
expect(error.messageVariables).toEqual({count: 1000});
|
||||
expect(await error.getResponse().json()).toEqual({
|
||||
code: APIErrorCodes.MAX_ACTIVE_THREADS,
|
||||
message: APIErrorCodes.MAX_ACTIVE_THREADS,
|
||||
max_active_threads: 1000,
|
||||
});
|
||||
});
|
||||
|
||||
it('answers an unready search index with 202 and a retry hint', async () => {
|
||||
const response = new SearchIndexNotReadyError(2).getResponse();
|
||||
expect(response.status).toBe(202);
|
||||
expect(await response.json()).toEqual({
|
||||
code: APIErrorCodes.SEARCH_INDEX_NOT_READY,
|
||||
message: APIErrorCodes.SEARCH_INDEX_NOT_READY,
|
||||
documents_indexed: 0,
|
||||
retry_after: 2,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
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}]);
|
||||
});
|
||||
|
||||
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,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PremiumPurchaseBlockedError', () => {
|
||||
it('keeps the reason and extra data of existing callers', () => {
|
||||
const error = new PremiumPurchaseBlockedError('existing_subscription', {subscription_status: 'active'});
|
||||
|
||||
expect(error.status).toBe(HttpStatus.FORBIDDEN);
|
||||
expect(error.toJSON()).toEqual({
|
||||
code: APIErrorCodes.PREMIUM_PURCHASE_BLOCKED,
|
||||
message: APIErrorCodes.PREMIUM_PURCHASE_BLOCKED,
|
||||
subscription_status: 'active',
|
||||
reason: 'existing_subscription',
|
||||
});
|
||||
});
|
||||
|
||||
it('includes the blocking provider when one is given', () => {
|
||||
const error = new PremiumPurchaseBlockedError('existing_subscription', {provider: 'app_store'});
|
||||
|
||||
expect(error.toJSON()).toEqual({
|
||||
code: APIErrorCodes.PREMIUM_PURCHASE_BLOCKED,
|
||||
message: APIErrorCodes.PREMIUM_PURCHASE_BLOCKED,
|
||||
provider: 'app_store',
|
||||
reason: 'existing_subscription',
|
||||
});
|
||||
});
|
||||
|
||||
it('defaults to the purchase_disabled reason with no provider', () => {
|
||||
expect(new PremiumPurchaseBlockedError().toJSON()).toEqual({
|
||||
code: APIErrorCodes.PREMIUM_PURCHASE_BLOCKED,
|
||||
message: APIErrorCodes.PREMIUM_PURCHASE_BLOCKED,
|
||||
reason: 'purchase_disabled',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('IpBannedError', () => {
|
||||
it('passes the appeal address through to the data and the message', () => {
|
||||
const error = new IpBannedError({
|
||||
ipAddress: '203.0.113.20',
|
||||
kind: 'temporary_24h',
|
||||
expiresAt: new Date('2026-10-01T23:30:00Z'),
|
||||
appealEmail: '[email protected]',
|
||||
productName: 'Fluxer',
|
||||
});
|
||||
expect(error.code).toBe(APIErrorCodes.GLOBAL_IP_TEMPORARILY_BANNED);
|
||||
expect(error.data).toEqual({
|
||||
ip_address: '203.0.113.20',
|
||||
appeal_email: '[email protected]',
|
||||
appeals_supported: true,
|
||||
ban_kind: 'temporary_24h',
|
||||
expires_at: '2026-10-01T23:30:00.000Z',
|
||||
});
|
||||
expect(error.messageVariables).toEqual({
|
||||
ipAddress: '203.0.113.20',
|
||||
appealEmail: '[email protected]',
|
||||
product_name: 'Fluxer',
|
||||
});
|
||||
});
|
||||
|
||||
it('has no appeal address of its own', () => {
|
||||
const error = new IpBannedError({
|
||||
ipAddress: '203.0.113.20',
|
||||
kind: 'permanent',
|
||||
appealEmail: null,
|
||||
productName: 'Example Chat',
|
||||
});
|
||||
expect(error.code).toBe(APIErrorCodes.GLOBAL_IP_BANNED);
|
||||
expect(error.data).toMatchObject({appeal_email: null, appeals_supported: true, expires_at: null});
|
||||
expect(error.messageVariables).toEqual({
|
||||
ipAddress: '203.0.113.20',
|
||||
appealEmail: null,
|
||||
product_name: 'Example Chat',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,85 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {FluxerError} from '@fluxer/errors/src/FluxerError';
|
||||
import {HTTPException} from 'hono/http-exception';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
describe('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',
|
||||
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);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -1,75 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {HttpStatus} from '@fluxer/constants/src/HttpConstants';
|
||||
import {FluxerError} from '@fluxer/errors/src/FluxerError';
|
||||
import {
|
||||
BadGatewayError,
|
||||
BadRequestError,
|
||||
ConflictError,
|
||||
ForbiddenError,
|
||||
GatewayTimeoutError,
|
||||
InternalServerError,
|
||||
NotFoundError,
|
||||
ServiceUnavailableError,
|
||||
UnauthorizedError,
|
||||
} from '@fluxer/errors/src/HttpErrors';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
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'],
|
||||
['CONFLICT', ConflictError, HttpStatus.CONFLICT, 'Conflict'],
|
||||
['INTERNAL_SERVER_ERROR', InternalServerError, HttpStatus.INTERNAL_SERVER_ERROR, 'Internal Server Error'],
|
||||
['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,
|
||||
});
|
||||
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',
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps the default message when only the code changes', () => {
|
||||
expect(new ErrorClass({code: 'CUSTOM_ERROR'}).toJSON()).toEqual({code: 'CUSTOM_ERROR', message});
|
||||
});
|
||||
|
||||
it('keeps the default code when only the message changes', () => {
|
||||
expect(new ErrorClass({message: 'Custom message'}).toJSON()).toEqual({code, message: 'Custom message'});
|
||||
});
|
||||
|
||||
it('does not replace explicitly empty overrides with defaults', () => {
|
||||
expect(new ErrorClass({code: '', message: ''}).toJSON()).toEqual({code: '', message: ''});
|
||||
});
|
||||
});
|
||||
@@ -1,80 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {HttpStatus} from '@fluxer/constants/src/HttpConstants';
|
||||
import {FluxerError} from '@fluxer/errors/src/FluxerError';
|
||||
import {ValidationError} from '@fluxer/errors/src/ValidationError';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
describe('ValidationError', () => {
|
||||
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'}],
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{errors: []},
|
||||
{
|
||||
errors: [
|
||||
{path: 'email', code: 'REQUIRED', message: 'Email is required'},
|
||||
{path: 'password', code: 'WEAK', message: 'Password is too weak'},
|
||||
],
|
||||
},
|
||||
])('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});
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
|
||||
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
|
||||
|
||||
export class CannotShrinkReservedSlotsError extends BadRequestError {
|
||||
constructor(reservedSlotIndices: Array<number>) {
|
||||
super({
|
||||
code: APIErrorCodes.CANNOT_SHRINK_RESERVED_SLOTS,
|
||||
messageVariables: {
|
||||
count: reservedSlotIndices.length,
|
||||
indices: reservedSlotIndices.join(', '),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -26,26 +26,3 @@ export function createJsonErrorResponse(options: JsonErrorResponseOptions): Resp
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function createXmlErrorResponse(status: number, code: string, message: string): Response {
|
||||
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Error>
|
||||
<Code>${escapeXml(code)}</Code>
|
||||
<Message>${escapeXml(message)}</Message>
|
||||
</Error>`;
|
||||
return new Response(xml, {
|
||||
status,
|
||||
headers: {
|
||||
'Content-Type': MimeType.XML,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function escapeXml(value: string): string {
|
||||
return value
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
@@ -375,9 +375,6 @@ export const ErrorCodeToI18nKey = {
|
||||
[ValidationErrorCodes.NO_FILE_FOR_ATTACHMENT_METADATA]:
|
||||
'attachments_and_uploads.no_file_uploaded_for_attachment_metadata',
|
||||
[ValidationErrorCodes.NO_UPLOADED_PARTS_TO_FINALIZE]: 'attachments_and_uploads.no_uploaded_parts_to_finalize',
|
||||
[ValidationErrorCodes.ATTACHMENT_FIELDS_REQUIRED]: 'attachments_and_uploads.attachment_fields_required',
|
||||
[ValidationErrorCodes.ATTACHMENT_IDS_MUST_BE_VALID_INTEGERS]:
|
||||
'attachments_and_uploads.attachment_ids_must_be_valid_integers',
|
||||
[ValidationErrorCodes.ATTACHMENTS_NOT_ALLOWED_FOR_MESSAGE]:
|
||||
'attachments_and_uploads.attachments_not_allowed_for_message',
|
||||
[ValidationErrorCodes.DUPLICATE_ATTACHMENT_IDS_NOT_ALLOWED]:
|
||||
@@ -398,7 +395,6 @@ export const ErrorCodeToI18nKey = {
|
||||
[ValidationErrorCodes.MULTIPLE_FILES_FOR_INDEX_NOT_ALLOWED]:
|
||||
'attachments_and_uploads.multiple_files_for_index_not_allowed',
|
||||
[ValidationErrorCodes.NO_VALID_MEDIA_IN_MESSAGE]: 'attachments_and_uploads.no_valid_media_in_message',
|
||||
[ValidationErrorCodes.SIZE_BYTES_MUST_BE_VALID_INTEGER]: 'attachments_and_uploads.size_bytes_must_be_valid_integer',
|
||||
[ValidationErrorCodes.TOO_MANY_FILES]: 'attachments_and_uploads.too_many_files',
|
||||
[ValidationErrorCodes.UNRESOLVED_ATTACHMENT_URL]: 'attachments_and_uploads.unresolved_attachment_url',
|
||||
[ValidationErrorCodes.UPLOADED_ATTACHMENT_NOT_FOUND]: 'attachments_and_uploads.uploaded_attachment_not_found',
|
||||
@@ -539,7 +535,6 @@ export const ErrorCodeToI18nKey = {
|
||||
[ValidationErrorCodes.VANITY_URL_INVALID_CHARACTERS]: 'vanity_urls.vanity_url_invalid_characters',
|
||||
[ValidationErrorCodes.WEBHOOK_NAME_LENGTH_INVALID]: 'webhooks.webhook_name_length_invalid',
|
||||
[ValidationErrorCodes.AT_LEAST_ONE_ENTRY_IS_REQUIRED]: 'limits.at_least_one_entry_required',
|
||||
[ValidationErrorCodes.BUCKET_IS_REQUIRED]: 'limits.bucket_required',
|
||||
[ValidationErrorCodes.CANNOT_PRELOAD_MORE_THAN_100_CHANNELS]: 'messages.cannot_preload_more_than_100_channels',
|
||||
[ValidationErrorCodes.INTEGER_OUT_OF_INT64_RANGE]: 'limits.integer_out_of_int64_range',
|
||||
[ValidationErrorCodes.INVALID_AUDIT_LOG_REASON]: 'admin_and_system.invalid_audit_log_reason',
|
||||
@@ -559,7 +554,6 @@ export const ErrorCodeToI18nKey = {
|
||||
'mfa_and_passkeys.must_enable_2fa_before_enforcing_for_mods',
|
||||
[ValidationErrorCodes.TOTP_NOT_ENABLED]: 'mfa_and_passkeys.totp_not_enabled',
|
||||
[ValidationErrorCodes.NAME_EMPTY_AFTER_NORMALIZATION]: 'names_and_normalization.name_empty_after_normalization',
|
||||
[ValidationErrorCodes.ROWS_IS_REQUIRED]: 'limits.rows_required',
|
||||
[ValidationErrorCodes.SNOWFLAKE_OUT_OF_RANGE]: 'misc.invalid_snowflake',
|
||||
[ValidationErrorCodes.STRING_LENGTH_EXACT]: 'limits.string_length_exact',
|
||||
[ValidationErrorCodes.TICKET_ALREADY_COMPLETED]: 'tickets.ticket_already_completed',
|
||||
|
||||
@@ -12,29 +12,12 @@ describe('ErrorI18n', () => {
|
||||
consoleWarnSpy.mockClear();
|
||||
});
|
||||
describe('constructor and initialization', () => {
|
||||
it('loads the default TypeScript catalog', () => {
|
||||
const message = getErrorMessageUnsafe('rate_limits.rate_limited', 'en-US');
|
||||
expect(message).toBe("You're being rate limited.");
|
||||
});
|
||||
it('handles missing default bundle gracefully', () => {
|
||||
const message = getErrorMessageUnsafe('nonexistent.key', 'en-US', undefined, 'Fallback message');
|
||||
expect(message).toBe('Fallback message');
|
||||
});
|
||||
});
|
||||
describe('getMessage() - basic retrieval', () => {
|
||||
it('returns message for valid key in default locale', () => {
|
||||
const message = getErrorMessageUnsafe('rate_limits.rate_limited', 'en-US');
|
||||
expect(message).toBe("You're being rate limited.");
|
||||
});
|
||||
it('maps API error codes to localized messages', () => {
|
||||
const message = getErrorMessageUnsafe('INVALID_FORM_BODY', 'en-US');
|
||||
expect(message).toBe('Invalid form body.');
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
it('returns message for valid key in supported locale', () => {
|
||||
const message = getErrorMessageUnsafe('rate_limits.rate_limited', 'fr');
|
||||
expect(message).toBe('Vous avez atteint la limite de requêtes.');
|
||||
});
|
||||
it('returns key when translation missing', () => {
|
||||
const message = getErrorMessageUnsafe('nonexistent.key', 'en-US');
|
||||
expect(message).toBe('nonexistent.key');
|
||||
@@ -42,20 +25,8 @@ describe('ErrorI18n', () => {
|
||||
'Missing translation for error message: nonexistent.key (locale: en-US)',
|
||||
);
|
||||
});
|
||||
it('returns fallbackMessage when provided and key missing', () => {
|
||||
const message = getErrorMessageUnsafe('nonexistent.key', 'en-US', undefined, 'Custom fallback');
|
||||
expect(message).toBe('Custom fallback');
|
||||
});
|
||||
});
|
||||
describe('getMessage() - locale handling', () => {
|
||||
it('normalizes en-GB locale to en-US', () => {
|
||||
const message = getErrorMessageUnsafe('rate_limits.rate_limited', 'en-GB');
|
||||
expect(message).toBe("You're being rate limited.");
|
||||
});
|
||||
it('normalizes en-CA locale to en-US', () => {
|
||||
const message = getErrorMessageUnsafe('rate_limits.rate_limited', 'en-CA');
|
||||
expect(message).toBe("You're being rate limited.");
|
||||
});
|
||||
it('falls back to en-US for unsupported locales', () => {
|
||||
const message = getErrorMessageUnsafe('rate_limits.rate_limited', 'de-DE');
|
||||
expect(message).toBe("You're being rate limited.");
|
||||
@@ -69,59 +40,6 @@ describe('ErrorI18n', () => {
|
||||
const message = getErrorMessageUnsafe('rate_limits.rate_limited', undefined);
|
||||
expect(message).toBe("You're being rate limited.");
|
||||
});
|
||||
it('loads locale on-demand when first accessed', () => {
|
||||
const message = getErrorMessageUnsafe('account.suspended_permanently', 'fr');
|
||||
expect(message).toBe('Ce compte a été suspendu définitivement.');
|
||||
});
|
||||
});
|
||||
describe('getMessage() - variable interpolation', () => {
|
||||
it('interpolates simple {variable} placeholders', () => {
|
||||
const message = getErrorMessageUnsafe('channels_and_guilds.invalid_channel_id', 'en-US', {
|
||||
channelId: '123456789',
|
||||
});
|
||||
expect(message).toBe('Invalid channel ID: 123456789.');
|
||||
});
|
||||
it('handles MessageFormat plural syntax', () => {
|
||||
const message = getErrorMessageUnsafe('rate_limits.username_changed_too_often', 'en-US', {
|
||||
minutes: 1,
|
||||
});
|
||||
expect(message).toBe("You've changed your username too often recently. Please try again in 1 minute.");
|
||||
});
|
||||
it('handles MessageFormat plural syntax for multiple values', () => {
|
||||
const message = getErrorMessageUnsafe('rate_limits.username_changed_too_often', 'en-US', {
|
||||
minutes: 5,
|
||||
});
|
||||
expect(message).toBe("You've changed your username too often recently. Please try again in 5 minutes.");
|
||||
});
|
||||
it('falls back to simple interpolation on MessageFormat failure', () => {
|
||||
const message = getErrorMessageUnsafe('channels_and_guilds.invalid_channel_id', 'en-US', {
|
||||
channelId: 'test-channel',
|
||||
});
|
||||
expect(message).toBe('Invalid channel ID: test-channel.');
|
||||
});
|
||||
it('returns raw message when no variables provided', () => {
|
||||
const message = getErrorMessageUnsafe('rate_limits.rate_limited', 'en-US');
|
||||
expect(message).toBe("You're being rate limited.");
|
||||
});
|
||||
it('handles complex nested error keys', () => {
|
||||
const message = getErrorMessageUnsafe('roles.invalid_role_id', 'en-US', {roleId: '999'});
|
||||
expect(message).toBe('Invalid role ID: 999.');
|
||||
});
|
||||
});
|
||||
describe('getMessage() - edge cases', () => {
|
||||
it('returns key when source message does not exist', () => {
|
||||
const message = getErrorMessageUnsafe('completely.made.up.key', 'en-US');
|
||||
expect(message).toBe('completely.made.up.key');
|
||||
});
|
||||
it('uses fallback when both key and fallbackMessage provided', () => {
|
||||
const message = getErrorMessageUnsafe('missing.key', 'en-US', {}, 'Fallback used');
|
||||
expect(message).toBe('Fallback used');
|
||||
});
|
||||
it('returns source message when locale translation missing but source exists', () => {
|
||||
const message = getErrorMessageUnsafe('rate_limits.rate_limited', 'xx-XX');
|
||||
expect(message).toBe("You're being rate limited.");
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith('Unsupported locale, falling back to en-US: xx-XX');
|
||||
});
|
||||
});
|
||||
describe('getMessageResult()', () => {
|
||||
it('returns error result for missing template', () => {
|
||||
@@ -132,56 +50,10 @@ describe('ErrorI18n', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('account limited message', () => {
|
||||
it('resolves ACCOUNT_LIMITED to its own message', () => {
|
||||
expect(getErrorMessageUnsafe('ACCOUNT_LIMITED', 'en-US')).toBe(
|
||||
'Messaging is paused on your account. Check your email for a quick step to continue.',
|
||||
);
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
it('falls back to the source message when the locale has no catalog', () => {
|
||||
expect(getErrorMessageUnsafe('account.limited', 'zz-ZZ')).toBe(getErrorMessageUnsafe('account.limited', 'en-US'));
|
||||
});
|
||||
});
|
||||
describe('global IP block messages', () => {
|
||||
const HOSTED = {ipAddress: '203.0.113.20', appealEmail: '[email protected]', product_name: 'Fluxer'};
|
||||
const SELF_HOSTED = {ipAddress: '203.0.113.20', appealEmail: null, product_name: 'Example Chat'};
|
||||
|
||||
it('names the appeal address when there is one', () => {
|
||||
expect(getErrorMessageUnsafe('GLOBAL_IP_BANNED', 'en-US', HOSTED)).toBe(
|
||||
'Your IP address 203.0.113.20 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.',
|
||||
);
|
||||
expect(getErrorMessageUnsafe('GLOBAL_IP_TEMPORARILY_BANNED', 'en-US', HOSTED)).toBe(
|
||||
'Your IP address 203.0.113.20 has been temporarily blocked from the Fluxer API. The block lifts on its own when it expires. If you think this is a mistake, contact [email protected] and include this IP address.',
|
||||
);
|
||||
});
|
||||
it('points at the instance administrators when there is no appeal address', () => {
|
||||
expect(getErrorMessageUnsafe('GLOBAL_IP_BANNED', 'en-US', SELF_HOSTED)).toBe(
|
||||
'Your IP address 203.0.113.20 has been permanently blocked from the Example Chat API by platform administrators. If you believe this is a mistake, contact the administrators of this instance to appeal. Include this IP address in your appeal.',
|
||||
);
|
||||
expect(getErrorMessageUnsafe('GLOBAL_IP_TEMPORARILY_BANNED', 'en-US', SELF_HOSTED)).toBe(
|
||||
'Your IP address 203.0.113.20 has been temporarily blocked from the Example Chat API. The block lifts on its own when it expires. If you think this is a mistake, contact the administrators of this instance and include this IP address.',
|
||||
);
|
||||
});
|
||||
it.each([
|
||||
['de', 'die Administratoren dieser Instanz'],
|
||||
['pt-BR', 'os administradores desta instância'],
|
||||
['ja', 'このインスタンスの管理者'],
|
||||
])('renders both variants in %s', (locale, administrators) => {
|
||||
for (const code of ['GLOBAL_IP_BANNED', 'GLOBAL_IP_TEMPORARILY_BANNED']) {
|
||||
const hosted = getErrorMessageUnsafe(code, locale, HOSTED);
|
||||
const neutral = getErrorMessageUnsafe(code, locale, SELF_HOSTED);
|
||||
expect(hosted).toContain('[email protected]');
|
||||
expect(hosted).toContain('203.0.113.20');
|
||||
expect(hosted).not.toContain(administrators);
|
||||
expect(neutral).toContain(administrators);
|
||||
expect(neutral).toContain('203.0.113.20');
|
||||
expect(neutral).not.toContain('@');
|
||||
expect(neutral).not.toMatch(/\bnull\b/);
|
||||
expect(neutral).not.toContain('{');
|
||||
}
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
it('names the instance and no mailbox in any locale when there is no appeal address', () => {
|
||||
for (const locale of Object.keys(ERROR_I18N_LOCALE_MESSAGES)) {
|
||||
for (const code of ['GLOBAL_IP_BANNED', 'GLOBAL_IP_TEMPORARILY_BANNED']) {
|
||||
@@ -203,25 +75,6 @@ describe('ErrorI18n', () => {
|
||||
const ENGLISH_NEUTRAL =
|
||||
'Payment processing encountered an error. Please try again or contact the administrators of this instance.';
|
||||
|
||||
it('keeps the hosted text and names the instance administrators on a self-hosted instance', () => {
|
||||
expect(getErrorMessageUnsafe('STRIPE_ERROR', 'en-US', HOSTED)).toBe(
|
||||
'Payment processing encountered an error. Please try again or contact support.',
|
||||
);
|
||||
expect(getErrorMessageUnsafe('STRIPE_ERROR', 'en-US', SELF_HOSTED)).toBe(ENGLISH_NEUTRAL);
|
||||
});
|
||||
it.each([
|
||||
['de', 'kontaktiere den Support', 'die Administratoren dieser Instanz'],
|
||||
['pt-BR', 'com o suporte', 'os administradores desta instância'],
|
||||
['ja', 'サポートにお問い合わせください', 'このインスタンスの管理者'],
|
||||
])('renders both variants in %s', (locale, support, administrators) => {
|
||||
const hosted = getErrorMessageUnsafe('STRIPE_ERROR', locale, HOSTED);
|
||||
const neutral = getErrorMessageUnsafe('STRIPE_ERROR', locale, SELF_HOSTED);
|
||||
expect(hosted).toContain(support);
|
||||
expect(hosted).not.toContain(administrators);
|
||||
expect(neutral).toContain(administrators);
|
||||
expect(neutral).not.toContain(support);
|
||||
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
it('has a translated neutral variant in every locale', () => {
|
||||
for (const locale of Object.keys(ERROR_I18N_LOCALE_MESSAGES)) {
|
||||
const hosted = getErrorMessageUnsafe('STRIPE_ERROR', locale, HOSTED);
|
||||
|
||||
@@ -46,10 +46,6 @@ export const ERROR_I18N_MESSAGES = {
|
||||
'admin_and_system.update_failed': "We couldn't update the resource. Please try again.",
|
||||
'admin_and_system.user_must_be_bot_for_system_user': 'User must be a bot to be marked as a system user.',
|
||||
'age_verification.already_verified': "You've already completed age verification.",
|
||||
'attachments_and_uploads.attachment_fields_required':
|
||||
'`attachment_id`, `channel_id`, `message_id`, and `expires_at` are required.',
|
||||
'attachments_and_uploads.attachment_ids_must_be_valid_integers':
|
||||
'`attachment_id`, `channel_id`, and `message_id` must be valid integers.',
|
||||
'attachments_and_uploads.attachment_must_be_image':
|
||||
'Attachment "{filename}" must be an image file (png, jpg, jpeg, webp, or gif).',
|
||||
'attachments_and_uploads.attachment_not_found_in_message':
|
||||
@@ -84,7 +80,6 @@ export const ERROR_I18N_MESSAGES = {
|
||||
'attachments_and_uploads.preview_must_be_jpeg': 'Preview must be JPEG.',
|
||||
'attachments_and_uploads.referenced_attachment_not_found':
|
||||
'Referenced attachment "{filename}" wasn\'t found in message attachments.',
|
||||
'attachments_and_uploads.size_bytes_must_be_valid_integer': '`size_bytes` must be a valid integer.',
|
||||
'attachments_and_uploads.too_many_files':
|
||||
'Too many files. Maximum {maxFiles, plural, one {# file} other {# files}} allowed.',
|
||||
'attachments_and_uploads.unresolved_attachment_url':
|
||||
@@ -344,13 +339,11 @@ export const ERROR_I18N_MESSAGES = {
|
||||
"You've reached the maximum of {count, plural, one {# emoji} other {# emojis}}.",
|
||||
'limits.at_least_one_entry_required': 'At least one entry is required.',
|
||||
'limits.base64_length_invalid': 'Base64 string length must be between {min} and {maxLength} characters.',
|
||||
'limits.bucket_required': '`bucket` is required.',
|
||||
'limits.cannot_shrink_reserved_slots': "You can't shrink reserved slots.",
|
||||
'limits.cannot_specify_before_and_after': "You can't specify both `before` and `after`.",
|
||||
'limits.content_exceeds_max_length':
|
||||
'Text must not exceed {maxLength, plural, one {# character} other {# characters}}.',
|
||||
'limits.integer_out_of_int64_range': 'Integer value is out of the valid int64 range.',
|
||||
'limits.rows_required': '`rows` is required.',
|
||||
'limits.string_length_exact': 'String must be exactly {length, plural, one {# character} other {# characters}}.',
|
||||
'limits.string_length_invalid': 'String length must be between {min} and {max} characters.',
|
||||
'limits.synced_preferences_too_large': 'Synced preferences exceed {max_bytes} bytes.',
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_AR_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "لم نتمكن من تحديث المورد. حاول مجددًا.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "يجب أن يكون المستخدم بوتًا ليتم تحديده كمستخدم نظام.",
|
||||
"age_verification.already_verified": "لقد أكملت التحقق من العمر بالفعل.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id` و`channel_id` و`message_id` و`expires_at` مطلوبة.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "يجب أن تكون `attachment_id` و`channel_id` و`message_id` أعدادًا صحيحة صالحة.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "يجب أن يكون المرفق \"{filename}\" ملف صورة (png أو jpg أو jpeg أو webp أو gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "لم يتم العثور على المرفق بالمعرف {attachmentId} في الرسالة.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "المرفقات غير مسموح بها لهذه الرسالة.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_AR_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "لم يتم العثور على وسائط صالحة في الرسالة.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "يجب أن تكون المعاينة بصيغة JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "لم يتم العثور على المرفق المشار إليه \"{filename}\" في مرفقات الرسالة.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "يجب أن يكون `size_bytes` عددًا صحيحًا صالحًا.",
|
||||
"attachments_and_uploads.too_many_files": "عدد كبير جدًا من الملفات. الحد الأقصى المسموح به هو {maxFiles, plural, zero {# ملف} one {ملف واحد} two {ملفان} few {# ملفات} many {# ملفًا} other {# ملف}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "تم اكتشاف عنوان URL بصيغة `attachment://` لم يتم حله. كان ينبغي حله قبل معالجة التضمين.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "لم يتم العثور على المرفق المرفوع {filename}.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_AR_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "لقد وصلت إلى الحد الأقصى وهو {count, plural, zero {# إيموجي} one {إيموجي واحد} two {إيموجيان} few {# إيموجيات} many {# إيموجي} other {# إيموجي}}.",
|
||||
"limits.at_least_one_entry_required": "مطلوب إدخال واحد على الأقل.",
|
||||
"limits.base64_length_invalid": "يجب أن يتراوح طول سلسلة Base64 بين {min} و{maxLength} حرفًا.",
|
||||
"limits.bucket_required": "`bucket` مطلوب.",
|
||||
"limits.cannot_shrink_reserved_slots": "لا يمكنك تقليص الخانات المحجوزة.",
|
||||
"limits.cannot_specify_before_and_after": "لا يمكنك تحديد `before` و`after` معًا.",
|
||||
"limits.content_exceeds_max_length": "يجب ألا يتجاوز طول النص {maxLength, plural, zero {# حرف} one {حرفًا واحدًا} two {حرفين} few {# أحرف} many {# حرفًا} other {# حرف}}.",
|
||||
"limits.integer_out_of_int64_range": "قيمة العدد الصحيح خارج النطاق المسموح به لـint64.",
|
||||
"limits.rows_required": "`rows` مطلوب.",
|
||||
"limits.string_length_exact": "يجب أن تحتوي السلسلة على {length, plural, zero {# حرف} one {حرف واحد} two {حرفين} few {# أحرف} many {# حرفًا} other {# حرف}} بالضبط.",
|
||||
"limits.string_length_invalid": "يجب أن يكون طول السلسلة بين {min} و{max} حرفًا.",
|
||||
"limits.synced_preferences_too_large": "تتجاوز التفضيلات المتزامنة الحد الأقصى وهو {max_bytes} بايت.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_BG_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Не успяхме да актуализираме ресурса. Опитай отново.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Потребителят трябва да бъде бот, за да бъде маркиран като системен потребител.",
|
||||
"age_verification.already_verified": "Вече премина проверката за възраст.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` и `expires_at` са задължителни.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` и `message_id` трябва да са валидни цели числа.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Прикаченият файл \"{filename}\" трябва да е изображение (png, jpg, jpeg, webp или gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Прикаченият файл с ID {attachmentId} не беше намерен в съобщението.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "За това съобщение не са разрешени прикачени файлове.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_BG_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Не е намерена валидна медия в съобщението.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Изображението за преглед трябва да е JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Посоченият прикачен файл \"{filename}\" не е намерен в прикачените файлове на съобщението.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` трябва да е валидно цяло число.",
|
||||
"attachments_and_uploads.too_many_files": "Твърде много файлове. Разрешени са максимум {maxFiles} {maxFiles, plural, one {файл} other {файла}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Открит е неразрешен `attachment://` URL. Това трябваше да бъде разрешено преди обработката на вградените елементи.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Каченият прикачен файл {filename} не е намерен.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_BG_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Достигна максимума от {count} {count, plural, one {емоджи} other {емоджита}}.",
|
||||
"limits.at_least_one_entry_required": "Необходим е поне един запис.",
|
||||
"limits.base64_length_invalid": "Дължината на Base64 низа трябва да е между {min} и {maxLength} знака.",
|
||||
"limits.bucket_required": "Изисква се `bucket`.",
|
||||
"limits.cannot_shrink_reserved_slots": "Не можеш да намалиш броя на запазените слотове.",
|
||||
"limits.cannot_specify_before_and_after": "Не можеш да зададеш едновременно `before` и `after`.",
|
||||
"limits.content_exceeds_max_length": "Текстът не може да надвишава {maxLength} {maxLength, plural, one {знак} other {знака}}.",
|
||||
"limits.integer_out_of_int64_range": "Стойността е извън допустимия диапазон за int64.",
|
||||
"limits.rows_required": "Изискват се `rows`.",
|
||||
"limits.string_length_exact": "Низът трябва да съдържа точно {length} {length, plural, one {знак} other {знака}}.",
|
||||
"limits.string_length_invalid": "Низът трябва да е с дължина между {min} и {max} знака.",
|
||||
"limits.synced_preferences_too_large": "Синхронизираните настройки надвишават {max_bytes} байта.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_CS_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Nepodařilo se aktualizovat zdroj. Zkuste to znovu.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Uživatel musí být bot, aby mohl být označen jako systémový uživatel.",
|
||||
"age_verification.already_verified": "Ověření věku už bylo dokončeno.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` a `expires_at` jsou povinné.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` a `message_id` musí být platné celočíselné hodnoty.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Příloha „{filename}“ musí být obrázkový soubor (png, jpg, jpeg, webp nebo gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Příloha s ID {attachmentId} se ve zprávě nenašla.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Pro tuto zprávu nejsou povoleny přílohy.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_CS_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Ve zprávě nebylo nalezeno žádné platné médium.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Náhled musí být ve formátu JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Odkazovaná příloha \"{filename}\" nebyla nalezena mezi přílohami zprávy.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` musí být platné celé číslo.",
|
||||
"attachments_and_uploads.too_many_files": "Příliš mnoho souborů. Lze nahrát nejvýše {maxFiles, plural, one {# soubor} few {# soubory} many {# souboru} other {# souborů}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Nalezena nevyřešená adresa URL `attachment://`. Odkaz měl být vyřešen před zpracováním vloženého obsahu.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Nahraná příloha {filename} nebyla nalezena.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_CS_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Dosáhli jste limitu {count, plural, one {# emoji} few {# emoji} many {# emoji} other {# emoji}}.",
|
||||
"limits.at_least_one_entry_required": "Je vyžadován alespoň jeden záznam.",
|
||||
"limits.base64_length_invalid": "Řetězec Base64 musí mít {min} až {maxLength} znaků.",
|
||||
"limits.bucket_required": "`bucket` je povinný.",
|
||||
"limits.cannot_shrink_reserved_slots": "Nemůžete snížit počet rezervovaných slotů.",
|
||||
"limits.cannot_specify_before_and_after": "Nemůžete zadat `before` i `after` zároveň.",
|
||||
"limits.content_exceeds_max_length": "Text nesmí přesáhnout {maxLength, plural, one {# znak} few {# znaky} many {# znaku} other {# znaků}}.",
|
||||
"limits.integer_out_of_int64_range": "Celé číslo je mimo platný rozsah int64.",
|
||||
"limits.rows_required": "`rows` je povinný.",
|
||||
"limits.string_length_exact": "Řetězec musí mít přesně {length, plural, one {# znak} few {# znaky} many {# znaku} other {# znaků}}.",
|
||||
"limits.string_length_invalid": "Řetězec musí mít {min} až {max} znaků.",
|
||||
"limits.synced_preferences_too_large": "Synchronizované předvolby přesahují {max_bytes} bajtů.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_DA_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Ressourcen kunne ikke opdateres. Prøv igen.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Brugeren skal være en bot for at kunne markeres som systembruger.",
|
||||
"age_verification.already_verified": "Du har allerede gennemført aldersbekræftelse.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` og `expires_at` er påkrævet.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` og `message_id` skal være gyldige heltal.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Den vedhæftede fil \"{filename}\" skal være en billedfil (png, jpg, jpeg, webp eller gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Den vedhæftede fil med id {attachmentId} blev ikke fundet i beskeden.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Vedhæftede filer er ikke tilladt for denne besked.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_DA_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Ingen gyldige medier blev fundet i beskeden.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Forhåndsvisningen skal være i JPEG-format.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Den refererede vedhæftede fil \"{filename}\" blev ikke fundet i beskedens vedhæftede filer.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` skal være et gyldigt heltal.",
|
||||
"attachments_and_uploads.too_many_files": "For mange filer. Maksimalt {maxFiles} {maxFiles, plural, one {fil} other {filer}} tilladt.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Der blev fundet en uafklaret `attachment://`-URL. Den skulle have været afklaret før behandlingen af indlejringer.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Den uploadede vedhæftede fil {filename} blev ikke fundet.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_DA_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Du har nået grænsen på {count, plural, one {# emoji} other {# emojis}}.",
|
||||
"limits.at_least_one_entry_required": "Mindst ét element er nødvendigt.",
|
||||
"limits.base64_length_invalid": "Base64-strengens længde skal være mellem {min} og {maxLength} tegn.",
|
||||
"limits.bucket_required": "`bucket` er påkrævet.",
|
||||
"limits.cannot_shrink_reserved_slots": "Du kan ikke reducere antallet af reserverede pladser.",
|
||||
"limits.cannot_specify_before_and_after": "Du kan ikke bruge både `before` og `after`.",
|
||||
"limits.content_exceeds_max_length": "Teksten må ikke overstige {maxLength} {maxLength, plural, one {tegn} other {tegn}}.",
|
||||
"limits.integer_out_of_int64_range": "Heltalsværdien er uden for det gyldige int64-område.",
|
||||
"limits.rows_required": "`rows` er påkrævet.",
|
||||
"limits.string_length_exact": "Strengen skal være nøjagtig {length, plural, one {# tegn} other {# tegn}} lang.",
|
||||
"limits.string_length_invalid": "Strenglængden skal være mellem {min} og {max} tegn.",
|
||||
"limits.synced_preferences_too_large": "Synkroniserede indstillinger overskrider {max_bytes} bytes.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_DE_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Wir konnten die Ressource nicht aktualisieren. Bitte versuche es erneut.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Ein Benutzer muss ein Bot sein, um als Systembenutzer markiert zu werden.",
|
||||
"age_verification.already_verified": "Du hast die Altersbestätigung bereits abgeschlossen.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` und `expires_at` sind erforderlich.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` und `message_id` müssen gültige Ganzzahlen sein.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Der Anhang „{filename}“ muss eine Bilddatei sein (png, jpg, jpeg, webp oder gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Anhang mit der ID {attachmentId} wurde nicht in der Nachricht gefunden.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Anhänge sind für diese Nachricht nicht erlaubt.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_DE_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Es wurde kein gültiges Medium in der Nachricht gefunden.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Die Vorschau muss im JPEG-Format vorliegen.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Der referenzierte Anhang \"{filename}\" wurde in den Nachrichtenanhängen nicht gefunden.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` muss eine gültige Ganzzahl sein.",
|
||||
"attachments_and_uploads.too_many_files": "Zu viele Dateien. Maximal {maxFiles, plural, one {# Datei} other {# Dateien}} erlaubt.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Unaufgelöste `attachment://`-URL erkannt. Sie sollte vor der Einbettungsverarbeitung aufgelöst werden.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Der hochgeladene Anhang {filename} wurde nicht gefunden.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_DE_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Du hast die maximale Anzahl von {count, plural, one {# Emoji} other {# Emojis}} erreicht.",
|
||||
"limits.at_least_one_entry_required": "Mindestens ein Eintrag ist erforderlich.",
|
||||
"limits.base64_length_invalid": "Die Länge der Base64-Zeichenkette muss zwischen {min} und {maxLength} Zeichen liegen.",
|
||||
"limits.bucket_required": "`bucket` ist erforderlich.",
|
||||
"limits.cannot_shrink_reserved_slots": "Du kannst reservierte Slots nicht verkleinern.",
|
||||
"limits.cannot_specify_before_and_after": "Du kannst nicht gleichzeitig `before` und `after` angeben.",
|
||||
"limits.content_exceeds_max_length": "Text darf maximal {maxLength, plural, one {# Zeichen} other {# Zeichen}} lang sein.",
|
||||
"limits.integer_out_of_int64_range": "Der Ganzzahlwert liegt außerhalb des gültigen int64-Bereichs.",
|
||||
"limits.rows_required": "`rows` ist erforderlich.",
|
||||
"limits.string_length_exact": "Die Zeichenkette muss genau {length, plural, one {# Zeichen} other {# Zeichen}} lang sein.",
|
||||
"limits.string_length_invalid": "Die Länge der Zeichenkette muss zwischen {min} und {max} Zeichen liegen.",
|
||||
"limits.synced_preferences_too_large": "Synchronisierte Einstellungen überschreiten {max_bytes} Bytes.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_EL_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Δεν μπορέσαμε να ενημερώσουμε τον πόρο. Δοκίμασε ξανά.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Ο χρήστης πρέπει να είναι bot για να οριστεί ως χρήστης συστήματος.",
|
||||
"age_verification.already_verified": "Έχεις ήδη ολοκληρώσει την επαλήθευση ηλικίας.",
|
||||
"attachments_and_uploads.attachment_fields_required": "Τα `attachment_id`, `channel_id`, `message_id` και `expires_at` είναι υποχρεωτικά.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "Τα `attachment_id`, `channel_id` και `message_id` πρέπει να είναι έγκυροι ακέραιοι αριθμοί.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Το συνημμένο \"{filename}\" πρέπει να είναι αρχείο εικόνας (png, jpg, jpeg, webp ή gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Το συνημμένο με αναγνωριστικό {attachmentId} δεν βρέθηκε στο μήνυμα.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Δεν επιτρέπονται συνημμένα σε αυτό το μήνυμα.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_EL_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Δεν βρέθηκαν έγκυρα πολυμέσα στο μήνυμα.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Η προεπισκόπηση πρέπει να είναι JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Το αναφερόμενο συνημμένο \"{filename}\" δεν βρέθηκε στα συνημμένα του μηνύματος.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "Το `size_bytes` πρέπει να είναι ένας έγκυρος ακέραιος αριθμός.",
|
||||
"attachments_and_uploads.too_many_files": "Πάρα πολλά αρχεία. Επιτρέπονται έως και {maxFiles, plural, one {# αρχείο} other {# αρχεία}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Βρέθηκε ανεπίλυτη διεύθυνση URL `attachment://`. Θα έπρεπε να είχε επιλυθεί πριν από την επεξεργασία των ενσωματώσεων.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Το ανεβασμένο συνημμένο {filename} δεν βρέθηκε.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_EL_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Έχεις φτάσει το μέγιστο των {count} {count, plural, one {emoji} other {emoji}}.",
|
||||
"limits.at_least_one_entry_required": "Απαιτείται τουλάχιστον μία εγγραφή.",
|
||||
"limits.base64_length_invalid": "Το μήκος της συμβολοσειράς Base64 πρέπει να είναι μεταξύ {min} και {maxLength} χαρακτήρων.",
|
||||
"limits.bucket_required": "Το `bucket` απαιτείται.",
|
||||
"limits.cannot_shrink_reserved_slots": "Δεν μπορείς να μειώσεις τον αριθμό των δεσμευμένων θέσεων.",
|
||||
"limits.cannot_specify_before_and_after": "Δεν μπορείς να καθορίσεις ταυτόχρονα `before` και `after`.",
|
||||
"limits.content_exceeds_max_length": "Το κείμενο δεν πρέπει να υπερβαίνει τους {maxLength} {maxLength, plural, one {χαρακτήρα} other {χαρακτήρες}}.",
|
||||
"limits.integer_out_of_int64_range": "Η ακέραια τιμή είναι εκτός του έγκυρου εύρους int64.",
|
||||
"limits.rows_required": "Το `rows` απαιτείται.",
|
||||
"limits.string_length_exact": "Η συμβολοσειρά πρέπει να έχει ακριβώς {length, plural, one {# χαρακτήρα} other {# χαρακτήρες}}.",
|
||||
"limits.string_length_invalid": "Το μήκος της συμβολοσειράς πρέπει να είναι μεταξύ {min} και {max} χαρακτήρων.",
|
||||
"limits.synced_preferences_too_large": "Οι συγχρονισμένες προτιμήσεις υπερβαίνουν τα {max_bytes} bytes.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_EN_GB_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "We couldn't update the resource. Please try again.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "User must be a bot to be marked as a system user.",
|
||||
"age_verification.already_verified": "You've already completed age verification.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id`, and `expires_at` are required.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id`, and `message_id` must be valid integers.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Attachment \"{filename}\" must be an image file (png, jpg, jpeg, webp, or gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Attachment with ID {attachmentId} wasn't found in the message.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Attachments aren't allowed for this message.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_EN_GB_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "No valid media was found in the message.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Preview must be JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Referenced attachment \"{filename}\" wasn't found in message attachments.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` must be a valid integer.",
|
||||
"attachments_and_uploads.too_many_files": "Too many files. Maximum {maxFiles, plural, one {# file} other {# files}} allowed.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Unresolved `attachment://` URL detected. This should have been resolved before embed processing.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Uploaded attachment {filename} wasn't found.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_EN_GB_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "You've reached the maximum of {count, plural, one {# emoji} other {# emojis}}.",
|
||||
"limits.at_least_one_entry_required": "At least one entry is required.",
|
||||
"limits.base64_length_invalid": "Base64 string length must be between {min} and {maxLength} characters.",
|
||||
"limits.bucket_required": "`bucket` is required.",
|
||||
"limits.cannot_shrink_reserved_slots": "You can't shrink reserved slots.",
|
||||
"limits.cannot_specify_before_and_after": "You can't specify both `before` and `after`.",
|
||||
"limits.content_exceeds_max_length": "Text must not exceed {maxLength, plural, one {# character} other {# characters}}.",
|
||||
"limits.integer_out_of_int64_range": "Integer value is out of the valid int64 range.",
|
||||
"limits.rows_required": "`rows` is required.",
|
||||
"limits.string_length_exact": "String must be exactly {length, plural, one {# character} other {# characters}}.",
|
||||
"limits.string_length_invalid": "String length must be between {min} and {max} characters.",
|
||||
"limits.synced_preferences_too_large": "Synced preferences exceed {max_bytes} bytes.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_ES_419_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "No se pudo actualizar el recurso. Inténtalo de nuevo.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "El usuario debe ser un bot para ser marcado como usuario del sistema.",
|
||||
"age_verification.already_verified": "Ya completaste la verificación de edad.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` y `expires_at` son obligatorios.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` y `message_id` deben ser enteros válidos.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "El archivo adjunto \"{filename}\" debe ser un archivo de imagen (png, jpg, jpeg, webp o gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "No se encontró el archivo adjunto con ID {attachmentId} en el mensaje.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "No se permiten archivos adjuntos para este mensaje.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_ES_419_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "No se encontró contenido multimedia válido en el mensaje.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "La vista previa debe ser JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "El archivo adjunto referenciado \"{filename}\" no se encontró en los adjuntos del mensaje.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` debe ser un número entero válido.",
|
||||
"attachments_and_uploads.too_many_files": "Demasiados archivos. Se permite un máximo de {maxFiles, plural, one {# archivo} other {# archivos}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Se detectó una URL `attachment://` sin resolver. Debería haberse resuelto antes de procesar el contenido incrustado.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "No se encontró el archivo adjunto subido {filename}.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_ES_419_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Has alcanzado el máximo de {count} {count, plural, one {emoji} other {emojis}}.",
|
||||
"limits.at_least_one_entry_required": "Se requiere al menos una entrada.",
|
||||
"limits.base64_length_invalid": "La longitud de la cadena Base64 debe estar entre {min} y {maxLength} caracteres.",
|
||||
"limits.bucket_required": "`bucket` es obligatorio.",
|
||||
"limits.cannot_shrink_reserved_slots": "No puedes disminuir los espacios reservados.",
|
||||
"limits.cannot_specify_before_and_after": "No puedes especificar `before` y `after` a la vez.",
|
||||
"limits.content_exceeds_max_length": "El texto no debe exceder los {maxLength} {maxLength, plural, one {carácter} other {caracteres}}.",
|
||||
"limits.integer_out_of_int64_range": "El valor entero está fuera del rango válido de int64.",
|
||||
"limits.rows_required": "`rows` es requerido.",
|
||||
"limits.string_length_exact": "La cadena debe tener exactamente {length, plural, one {# carácter} many {# caracteres} other {# caracteres}}.",
|
||||
"limits.string_length_invalid": "La longitud de la cadena debe tener entre {min} y {max} caracteres.",
|
||||
"limits.synced_preferences_too_large": "Las preferencias sincronizadas superan los {max_bytes} bytes.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_ES_ES_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "No se pudo actualizar el recurso. Inténtalo de nuevo.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "El usuario debe ser un bot para ser marcado como usuario del sistema.",
|
||||
"age_verification.already_verified": "Ya has completado la verificación de edad.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` y `expires_at` son obligatorios.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` y `message_id` deben ser enteros válidos.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "El archivo adjunto \"{filename}\" debe ser un archivo de imagen (png, jpg, jpeg, webp o gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "No se encontró el archivo adjunto con ID {attachmentId} en el mensaje.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "No se permiten archivos adjuntos para este mensaje.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_ES_ES_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "No se encontró contenido multimedia válido en el mensaje.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "La vista previa debe ser JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "El archivo adjunto referenciado \"{filename}\" no se encontró entre los archivos adjuntos del mensaje.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` debe ser un número entero válido.",
|
||||
"attachments_and_uploads.too_many_files": "Demasiados archivos. Se permite un máximo de {maxFiles} {maxFiles, plural, one {archivo} other {archivos}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Se detectó una URL `attachment://` sin resolver. Debería haberse resuelto antes de procesar el contenido incrustado.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "No se encontró el archivo adjunto subido con el nombre {filename}.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_ES_ES_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Has alcanzado el máximo de {count} {count, plural, one {emoji} other {emojis}}.",
|
||||
"limits.at_least_one_entry_required": "Es necesaria al menos una entrada.",
|
||||
"limits.base64_length_invalid": "La longitud de la cadena Base64 debe estar entre {min} y {maxLength} caracteres.",
|
||||
"limits.bucket_required": "`bucket` es necesario.",
|
||||
"limits.cannot_shrink_reserved_slots": "No puedes reducir los espacios reservados.",
|
||||
"limits.cannot_specify_before_and_after": "No puedes especificar `before` y `after` a la vez.",
|
||||
"limits.content_exceeds_max_length": "El texto no debe exceder {maxLength} {maxLength, plural, one {carácter} other {caracteres}}.",
|
||||
"limits.integer_out_of_int64_range": "El valor entero está fuera del rango válido de int64.",
|
||||
"limits.rows_required": "`rows` es necesario.",
|
||||
"limits.string_length_exact": "La cadena debe tener exactamente {length, plural, one {# carácter} many {# caracteres} other {# caracteres}}.",
|
||||
"limits.string_length_invalid": "La cadena debe tener una longitud entre {min} y {max} caracteres.",
|
||||
"limits.synced_preferences_too_large": "Las preferencias sincronizadas superan los {max_bytes} bytes.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_FI_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Resurssia ei voitu päivittää. Yritä uudelleen.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Käyttäjän on oltava botti, jotta sen voi merkitä järjestelmäkäyttäjäksi.",
|
||||
"age_verification.already_verified": "Olet jo suorittanut iän vahvistuksen.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` ja `expires_at` ovat pakollisia.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "Seuraavien kenttien arvojen on oltava kelvollisia kokonaislukuja: `attachment_id`, `channel_id` ja `message_id`.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Liitteen \"{filename}\" on oltava kuvatiedosto (png, jpg, jpeg, webp tai gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Viestistä ei löytynyt liitettä {attachmentId}.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Tässä viestissä liitteet eivät ole sallittuja.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_FI_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Viestistä ei löytynyt kelvollista mediatiedostoa.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Esikatselun on oltava JPEG-muodossa.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Viitattua liitettä \"{filename}\" ei löytynyt viestin liitteiden joukosta.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes`-arvon on oltava kelvollinen kokonaisluku.",
|
||||
"attachments_and_uploads.too_many_files": "Liian monta tiedostoa. Sallittu enimmäismäärä on {maxFiles, plural, one {# tiedosto} other {# tiedostoa}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Ratkaisematon `attachment://`-URL havaittu. Se olisi tullut ratkaista ennen upotusten käsittelyä.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Ladattua liitettä {filename} ei löytynyt.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_FI_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Olet saavuttanut emojien enimmäismäärän: {count, plural, one {# emoji} other {# emojia}}.",
|
||||
"limits.at_least_one_entry_required": "Vähintään yksi merkintä vaaditaan.",
|
||||
"limits.base64_length_invalid": "Base64-merkkijonon pituuden on oltava {min}–{maxLength} merkkiä.",
|
||||
"limits.bucket_required": "`bucket` on pakollinen.",
|
||||
"limits.cannot_shrink_reserved_slots": "Et voi vähentää varattujen paikkojen määrää.",
|
||||
"limits.cannot_specify_before_and_after": "Et voi määrittää sekä `before`- että `after`-arvoja.",
|
||||
"limits.content_exceeds_max_length": "Tekstin pituus saa olla enintään {maxLength, plural, one {# merkki} other {# merkkiä}}.",
|
||||
"limits.integer_out_of_int64_range": "Kokonaislukuarvo on int64-alueen ulkopuolella.",
|
||||
"limits.rows_required": "`rows` on pakollinen.",
|
||||
"limits.string_length_exact": "Merkkijonon on oltava tasan {length, plural, one {# merkki} other {# merkkiä}} pitkä.",
|
||||
"limits.string_length_invalid": "Merkkijonon pituuden on oltava {min}–{max} merkkiä.",
|
||||
"limits.synced_preferences_too_large": "Synkronoidut asetukset ylittävät {max_bytes} tavua.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_FR_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Impossible de mettre à jour la ressource. Réessayez.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "L'utilisateur doit être un bot pour être marqué comme utilisateur système.",
|
||||
"age_verification.already_verified": "Vous avez déjà effectué la vérification de l’âge.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` et `expires_at` sont nécessaires.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` et `message_id` doivent être des entiers valides.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "La pièce jointe \"{filename}\" doit être un fichier image (png, jpg, jpeg, webp ou gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "La pièce jointe avec l'ID {attachmentId} n'a pas été trouvée dans le message.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Les pièces jointes ne sont pas autorisées pour ce message.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_FR_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Aucun média valide n'a été trouvé dans le message.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "L'aperçu doit être au format JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "La pièce jointe référencée \"{filename}\" n'a pas été trouvée dans les pièces jointes du message.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` doit être un entier valide.",
|
||||
"attachments_and_uploads.too_many_files": "Trop de fichiers. {maxFiles, plural, =1 {Un seul fichier est autorisé.} other {Le nombre maximal de fichiers autorisés est de #.}}",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Une URL `attachment://` non résolue a été détectée. Elle aurait dû être résolue avant le traitement du contenu intégré.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "La pièce jointe envoyée {filename} n'a pas été trouvée.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_FR_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Vous avez atteint le maximum de {count, plural, =1 {# emoji} other {# emojis}}.",
|
||||
"limits.at_least_one_entry_required": "Au moins un élément est requis.",
|
||||
"limits.base64_length_invalid": "La longueur de la chaîne Base64 doit être comprise entre {min} et {maxLength} caractères.",
|
||||
"limits.bucket_required": "`bucket` est obligatoire.",
|
||||
"limits.cannot_shrink_reserved_slots": "Vous ne pouvez pas réduire le nombre d’emplacements réservés.",
|
||||
"limits.cannot_specify_before_and_after": "Vous ne pouvez pas indiquer à la fois `before` et `after`.",
|
||||
"limits.content_exceeds_max_length": "Le texte ne doit pas dépasser {maxLength, plural, =1 {# caractère} other {# caractères}}.",
|
||||
"limits.integer_out_of_int64_range": "La valeur entière est en dehors de l'intervalle int64 valide.",
|
||||
"limits.rows_required": "`rows` est obligatoire.",
|
||||
"limits.string_length_exact": "La chaîne doit contenir exactement {length, plural, =1 {# caractère} other {# caractères}}.",
|
||||
"limits.string_length_invalid": "La longueur de la chaîne doit être comprise entre {min} et {max} caractères.",
|
||||
"limits.synced_preferences_too_large": "Les préférences synchronisées dépassent {max_bytes} octets.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_HE_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "לא הצלחנו לעדכן את המשאב. ניתן לנסות שוב.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "המשתמש חייב להיות בוט כדי להיות מוגדר כמשתמש מערכת.",
|
||||
"age_verification.already_verified": "כבר השלמת את אימות הגיל.",
|
||||
"attachments_and_uploads.attachment_fields_required": "יש לציין את `attachment_id`, `channel_id`, `message_id` ו-`expires_at`.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` ו-`message_id` חייבים להיות מספרים שלמים תקינים.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "הקובץ המצורף \"{filename}\" חייב להיות קובץ תמונה (png, jpg, jpeg, webp או gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "הקובץ המצורף עם המזהה {attachmentId} לא נמצא בהודעה.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "קבצים מצורפים אסורים בהודעה זו.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_HE_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "לא נמצא בהודעה קובץ מדיה תקין.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "התצוגה המקדימה חייבת להיות בפורמט JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "הקובץ המצורף \"{filename}\" שאליו יש הפניה לא נמצא בין הקבצים המצורפים להודעה.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` חייב להיות מספר שלם תקין.",
|
||||
"attachments_and_uploads.too_many_files": "יותר מדי קבצים. מותרים לכל היותר {maxFiles} {maxFiles, plural, one {קובץ} other {קבצים}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "זוהתה כתובת URL של `attachment://` שלא נפתרה. היה עליה להיפתר לפני עיבוד ההטמעה.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "הקובץ המצורף שהועלה {filename} לא נמצא.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_HE_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "הגעת למספר המרבי של {count} {count, plural, one {אימוג'י} other {אימוג'ים}}.",
|
||||
"limits.at_least_one_entry_required": "נדרשת רשומה אחת לפחות.",
|
||||
"limits.base64_length_invalid": "אורך מחרוזת Base64 חייב להיות בין {min} ל-{maxLength} תווים.",
|
||||
"limits.bucket_required": "`bucket` נדרש.",
|
||||
"limits.cannot_shrink_reserved_slots": "לא ניתן להקטין את המקומות השמורים.",
|
||||
"limits.cannot_specify_before_and_after": "לא ניתן לציין את `before` ואת `after` יחד.",
|
||||
"limits.content_exceeds_max_length": "הטקסט מוגבל ל-{maxLength} {maxLength, plural, one {תו} other {תווים}}.",
|
||||
"limits.integer_out_of_int64_range": "הערך השלם חורג מהטווח התקין של int64.",
|
||||
"limits.rows_required": "`rows` נדרש.",
|
||||
"limits.string_length_exact": "אורך המחרוזת חייב להיות בדיוק {length} {length, plural, one {תו} other {תווים}}.",
|
||||
"limits.string_length_invalid": "אורך המחרוזת חייב להיות בין {min} ל-{max} תווים.",
|
||||
"limits.synced_preferences_too_large": "ההעדפות המסונכרנות חורגות מ-{max_bytes} בתים.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_HI_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "रिसोर्स अपडेट नहीं किया जा सका। कृपया फिर से कोशिश करें।",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "सिस्टम यूज़र के रूप में मार्क किए जाने के लिए यूज़र का बॉट होना ज़रूरी है।",
|
||||
"age_verification.already_verified": "आपने पहले ही एज वेरिफ़िकेशन पूरा कर लिया है।",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id`, और `expires_at` ज़रूरी हैं।",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id`, और `message_id` मान्य इंटीजर होने चाहिए।",
|
||||
"attachments_and_uploads.attachment_must_be_image": "अटैचमेंट \"{filename}\" एक इमेज फ़ाइल (png, jpg, jpeg, webp, या gif) होना चाहिए।",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "ID {attachmentId} वाला अटैचमेंट मैसेज में नहीं मिला।",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "इस मैसेज के लिए अटैचमेंट की अनुमति नहीं है।",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_HI_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "मैसेज में कोई मान्य मीडिया नहीं मिला।",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "प्रीव्यू JPEG होना चाहिए।",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "जिस अटैचमेंट \"{filename}\" का ज़िक्र किया गया था, वह मैसेज अटैचमेंट में नहीं मिला।",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` मान्य इंटीजर होना चाहिए।",
|
||||
"attachments_and_uploads.too_many_files": "बहुत ज़्यादा फ़ाइलें। अधिकतम {maxFiles} {maxFiles, plural, one {फ़ाइल} other {फ़ाइल}} की अनुमति है।",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "अनसुलझा `attachment://` URL पाया गया। इसे एम्बेड प्रोसेसिंग से पहले हल किया जाना चाहिए था।",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "अपलोड किया गया अटैचमेंट {filename} नहीं मिला।",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_HI_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "आप अधिकतम {count} {count, plural, one {इमोजी} other {इमोजी}} तक पहुँच गए हैं।",
|
||||
"limits.at_least_one_entry_required": "कम से कम एक एंट्री ज़रूरी है।",
|
||||
"limits.base64_length_invalid": "Base64 स्ट्रिंग की लंबाई {min} और {maxLength} कैरेक्टर के बीच होनी चाहिए।",
|
||||
"limits.bucket_required": "`bucket` ज़रूरी है।",
|
||||
"limits.cannot_shrink_reserved_slots": "आप रिज़र्व किए गए स्लॉट कम नहीं कर सकते।",
|
||||
"limits.cannot_specify_before_and_after": "आप `before` और `after` दोनों को स्पेसिफाई नहीं कर सकते।",
|
||||
"limits.content_exceeds_max_length": "टेक्स्ट {maxLength} {maxLength, plural, one {कैरेक्टर} other {कैरेक्टर}} से ज़्यादा नहीं होना चाहिए।",
|
||||
"limits.integer_out_of_int64_range": "इंटीजर वैल्यू मान्य int64 रेंज से बाहर है।",
|
||||
"limits.rows_required": "`rows` ज़रूरी है।",
|
||||
"limits.string_length_exact": "स्ट्रिंग की लंबाई ठीक {length, plural, one {# कैरेक्टर} other {# कैरेक्टर}} होनी चाहिए।",
|
||||
"limits.string_length_invalid": "स्ट्रिंग की लंबाई {min} और {max} कैरेक्टर के बीच होनी चाहिए।",
|
||||
"limits.synced_preferences_too_large": "सिंक की गई सेटिंग्स {max_bytes} बाइट्स से ज़्यादा हैं।",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_HR_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Nismo mogli ažurirati resurs. Pokušaj ponovno.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Korisnik mora biti bot da bi bio označen kao sistemski korisnik.",
|
||||
"age_verification.already_verified": "Provjera dobi je već završena.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` i `expires_at` su obavezni.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` i `message_id` moraju biti valjani cijeli brojevi.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Privitak \"{filename}\" mora biti slika (png, jpg, jpeg, webp ili gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Privitak s ID-om {attachmentId} nije pronađen u poruci.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Privici nisu dopušteni za ovu poruku.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_HR_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Nije pronađen valjan medij u poruci.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Pretpregled mora biti JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Referencirani privitak \"{filename}\" nije pronađen u privicima poruke.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` mora biti valjan cijeli broj.",
|
||||
"attachments_and_uploads.too_many_files": "Previše datoteka. Dopušteno je najviše {maxFiles, plural, one {# datoteka} few {# datoteke} other {# datoteka}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Otkriven je nerazriješeni `attachment://` URL. Trebao je biti razriješen prije obrade ugrađenog sadržaja.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Preneseni privitak {filename} nije pronađen.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_HR_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Dosegnut je maksimum od {count, plural, one {# emojija} few {# emojija} other {# emojija}}.",
|
||||
"limits.at_least_one_entry_required": "Potreban je barem jedan unos.",
|
||||
"limits.base64_length_invalid": "Duljina Base64 niza mora biti između {min} i {maxLength} znakova.",
|
||||
"limits.bucket_required": "`bucket` je obavezan.",
|
||||
"limits.cannot_shrink_reserved_slots": "Ne možeš smanjiti rezervirane utore.",
|
||||
"limits.cannot_specify_before_and_after": "Ne možeš navesti i `before` i `after`.",
|
||||
"limits.content_exceeds_max_length": "Tekst ne smije prelaziti {maxLength, plural, one {# znak} few {# znaka} other {# znakova}}.",
|
||||
"limits.integer_out_of_int64_range": "Cjelobrojna vrijednost je izvan važećeg raspona za int64.",
|
||||
"limits.rows_required": "`rows` je obavezan.",
|
||||
"limits.string_length_exact": "Niz znakova mora imati točno {length, plural, one {# znak} few {# znaka} other {# znakova}}.",
|
||||
"limits.string_length_invalid": "Duljina niza mora biti između {min} i {max} znakova.",
|
||||
"limits.synced_preferences_too_large": "Sinkronizirane postavke premašuju {max_bytes} bajtova.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_HU_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Nem sikerült frissíteni az erőforrást. Próbáld újra.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Csak botfelhasználó jelölhető meg rendszerfelhasználóként.",
|
||||
"age_verification.already_verified": "Már elvégezted a korhatár-ellenőrzést.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` és `expires_at` megadása kötelező.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` és `message_id` érvényes egész számoknak kell lenniük.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "A(z) \"{filename}\" mellékletnek képfájlnak kell lennie (png, jpg, jpeg, webp vagy gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "A(z) {attachmentId} azonosítójú mellékletet nem találtuk az üzenetben.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Ehhez az üzenethez nem engedélyezettek a mellékletek.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_HU_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Nem található érvényes média az üzenetben.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Az előnézet csak JPEG formátumú lehet.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "A hivatkozott \"{filename}\" melléklet nem szerepel az üzenet mellékletei között.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "A `size_bytes` értékének érvényes egész számnak kell lennie.",
|
||||
"attachments_and_uploads.too_many_files": "Túl sok fájl. Legfeljebb {maxFiles, plural, one {# fájl} other {# fájl}} engedélyezett.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Feloldatlan `attachment://` URL található. Ezt a beágyazott tartalom feldolgozása előtt fel kellett volna oldani.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "A feltöltött \"{filename}\" melléklet nem található.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_HU_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Elérted az emojik megengedett számát ({count, plural, one {#} other {#}}).",
|
||||
"limits.at_least_one_entry_required": "Legalább egy bejegyzés szükséges.",
|
||||
"limits.base64_length_invalid": "A Base64 karakterlánc hossza {min} és {maxLength} karakter közötti lehet.",
|
||||
"limits.bucket_required": "`bucket` szükséges.",
|
||||
"limits.cannot_shrink_reserved_slots": "A lefoglalt helyek számát nem csökkentheted.",
|
||||
"limits.cannot_specify_before_and_after": "Nem adhatod meg egyszerre a `before` és az `after` paramétert.",
|
||||
"limits.content_exceeds_max_length": "A szöveg legfeljebb {maxLength, plural, one {# karakter} other {# karakter}} hosszú lehet.",
|
||||
"limits.integer_out_of_int64_range": "Az egész érték kívül esik az érvényes int64-tartományon.",
|
||||
"limits.rows_required": "`rows` szükséges.",
|
||||
"limits.string_length_exact": "A karakterlánc hossza pontosan {length, plural, one {# karakter} other {# karakter}} legyen.",
|
||||
"limits.string_length_invalid": "A karakterlánc hossza {min} és {max} karakter közötti lehet.",
|
||||
"limits.synced_preferences_too_large": "A szinkronizált beállítások mérete meghaladja a {max_bytes} bájtot.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_ID_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Tidak bisa memperbarui sumber daya. Coba lagi.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Pengguna harus bot untuk ditandai sebagai pengguna sistem.",
|
||||
"age_verification.already_verified": "Kamu sudah menyelesaikan verifikasi usia.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id`, dan `expires_at` diperlukan.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id`, dan `message_id` harus berupa bilangan bulat yang valid.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Lampiran \"{filename}\" harus berupa gambar (png, jpg, jpeg, webp, atau gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Lampiran dengan ID {attachmentId} tidak ditemukan di pesan.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Lampiran tidak diizinkan untuk pesan ini.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_ID_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Tidak ada media yang valid di pesan.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Pratinjau harus berupa JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Lampiran \"{filename}\" yang dirujuk tidak ditemukan di lampiran pesan.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` harus berupa bilangan bulat yang valid.",
|
||||
"attachments_and_uploads.too_many_files": "Terlalu banyak file. Maksimal {maxFiles} {maxFiles, plural, other {file}} diizinkan.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Terdeteksi URL `attachment://` yang belum diselesaikan. Ini seharusnya sudah diselesaikan sebelum pemrosesan sematan.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Lampiran {filename} yang diunggah tidak ditemukan.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_ID_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Kamu sudah mencapai batas maksimum {count} {count, plural, other {emoji}}.",
|
||||
"limits.at_least_one_entry_required": "Minimal satu entri diperlukan.",
|
||||
"limits.base64_length_invalid": "Panjang string Base64 harus antara {min} dan {maxLength} karakter.",
|
||||
"limits.bucket_required": "`bucket` diperlukan.",
|
||||
"limits.cannot_shrink_reserved_slots": "Kamu tidak bisa mengurangi slot yang dicadangkan.",
|
||||
"limits.cannot_specify_before_and_after": "Kamu tidak bisa menentukan `before` dan `after` sekaligus.",
|
||||
"limits.content_exceeds_max_length": "Teks tidak boleh melebihi {maxLength} {maxLength, plural, other {karakter}}.",
|
||||
"limits.integer_out_of_int64_range": "Nilai integer di luar rentang int64 yang valid.",
|
||||
"limits.rows_required": "`rows` diperlukan.",
|
||||
"limits.string_length_exact": "String harus berisi tepat {length, plural, other {# karakter}}.",
|
||||
"limits.string_length_invalid": "Panjang string harus antara {min} dan {max} karakter.",
|
||||
"limits.synced_preferences_too_large": "Preferensi yang disinkronkan melebihi {max_bytes} byte.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_IT_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Non è stato possibile aggiornare la risorsa. Riprova.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "L'utente deve essere un bot per essere contrassegnato come utente di sistema.",
|
||||
"age_verification.already_verified": "Hai già completato la verifica dell'età.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` e `expires_at` sono richiesti.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` e `message_id` devono essere interi validi.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "L'allegato \"{filename}\" deve essere un file immagine (png, jpg, jpeg, webp o gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "L'allegato con ID {attachmentId} non è stato trovato nel messaggio.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Gli allegati non sono consentiti per questo messaggio.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_IT_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Nessun media valido è stato trovato nel messaggio.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "L'anteprima deve essere JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "L'allegato di riferimento \"{filename}\" non è stato trovato negli allegati del messaggio.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` deve essere un numero intero valido.",
|
||||
"attachments_and_uploads.too_many_files": "Troppi file. Il limite è di {maxFiles, plural, one {# file} other {# file}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Rilevato URL `attachment://` non risolto. Avrebbe dovuto essere risolto prima dell’elaborazione del contenuto incorporato.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "L'allegato caricato {filename} non è stato trovato.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_IT_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Hai raggiunto il massimo di {count, plural, one {# emoji} other {# emoji}}.",
|
||||
"limits.at_least_one_entry_required": "È richiesta almeno una voce.",
|
||||
"limits.base64_length_invalid": "La lunghezza della stringa base64 deve essere compresa tra {min} e {maxLength} caratteri.",
|
||||
"limits.bucket_required": "`bucket` è richiesto.",
|
||||
"limits.cannot_shrink_reserved_slots": "Non puoi diminuire gli slot riservati.",
|
||||
"limits.cannot_specify_before_and_after": "Non puoi specificare sia `before` che `after`.",
|
||||
"limits.content_exceeds_max_length": "Il testo non può superare {maxLength, plural, one {# carattere} other {# caratteri}}.",
|
||||
"limits.integer_out_of_int64_range": "Il valore intero è fuori dall'intervallo valido di int64.",
|
||||
"limits.rows_required": "`rows` è richiesto.",
|
||||
"limits.string_length_exact": "La stringa deve contenere esattamente {length, plural, one {# carattere} many {# caratteri} other {# caratteri}}.",
|
||||
"limits.string_length_invalid": "La lunghezza della stringa deve essere compresa tra {min} e {max} caratteri.",
|
||||
"limits.synced_preferences_too_large": "Le preferenze sincronizzate superano i {max_bytes} byte.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_JA_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "リソースを更新できませんでした。もう一度お試しください。",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "システムユーザーとしてマークするには、ユーザーはボットである必要があります。",
|
||||
"age_verification.already_verified": "年齢確認はすでに完了しています。",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`、`channel_id`、`message_id`、`expires_at`は必須です。",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`、`channel_id`、`message_id`は有効な整数である必要があります。",
|
||||
"attachments_and_uploads.attachment_must_be_image": "添付ファイル「{filename}」は画像ファイル(png、jpg、jpeg、webp、またはgif)である必要があります。",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "添付ファイル(ID: {attachmentId})がメッセージ内に見つかりませんでした。",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "このメッセージには添付ファイルを使用できません。",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_JA_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "メッセージ内に有効なメディアが見つかりませんでした。",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "プレビューはJPEG形式にしてください。",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "参照された添付ファイル「{filename}」がメッセージの添付ファイルに見つかりませんでした。",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes`は有効な整数である必要があります。",
|
||||
"attachments_and_uploads.too_many_files": "ファイルが多すぎます。最大{maxFiles, plural, other {#個}}のファイルまでです。",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "未解決の`attachment://` URLが検出されました。これは埋め込み処理の前に解決されているはずです。",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "アップロードされた添付ファイル「{filename}」が見つかりませんでした。",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_JA_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "絵文字の上限{count, plural, other {#個}}に達しました。",
|
||||
"limits.at_least_one_entry_required": "1つ以上のエントリが必要です。",
|
||||
"limits.base64_length_invalid": "Base64文字列の長さは{min}~{maxLength}文字である必要があります。",
|
||||
"limits.bucket_required": "`bucket`は必須です。",
|
||||
"limits.cannot_shrink_reserved_slots": "予約済みスロットを減らすことはできません。",
|
||||
"limits.cannot_specify_before_and_after": "`before`と`after`は同時に指定できません。",
|
||||
"limits.content_exceeds_max_length": "テキストは{maxLength, plural, other {#文字}}を超えることはできません。",
|
||||
"limits.integer_out_of_int64_range": "整数値がint64の有効範囲外です。",
|
||||
"limits.rows_required": "`rows`は必須です。",
|
||||
"limits.string_length_exact": "文字列はちょうど{length, plural, other {#文字}}である必要があります。",
|
||||
"limits.string_length_invalid": "文字列の長さは{min}~{max}文字である必要があります。",
|
||||
"limits.synced_preferences_too_large": "同期された設定が{max_bytes}バイトを超えています。",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_KO_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "리소스를 업데이트하지 못했어요. 다시 시도해 주세요.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "시스템 사용자로 표시하려면 봇이어야 해요.",
|
||||
"age_verification.already_verified": "연령 인증을 이미 완료했어요.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id`, `expires_at`이 모두 필요해요.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id`, `message_id`는 모두 유효한 정수여야 해요.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "첨부 파일 \"{filename}\"은 이미지 파일(png, jpg, jpeg, webp 또는 gif)이어야 해요.",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "ID가 {attachmentId}인 첨부 파일을 메시지에서 찾을 수 없어요.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "이 메시지에는 첨부 파일을 추가할 수 없어요.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_KO_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "메시지에서 유효한 미디어를 찾을 수 없어요.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "미리보기는 JPEG 형식이어야 해요.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "참조된 첨부 파일 \"{filename}\"을 메시지 첨부 파일에서 찾을 수 없어요.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes`는 유효한 정수여야 해요.",
|
||||
"attachments_and_uploads.too_many_files": "파일이 너무 많아요. 최대 {maxFiles, plural, other {#개}}까지 허용돼요.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "미해결 `attachment://` URL이 감지되었어요. 임베드 처리 전에 해결되었어야 해요.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "업로드한 첨부 파일 {filename}을 찾을 수 없어요.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_KO_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "최대 {count, plural, other {#개}}의 이모지에 도달했어요.",
|
||||
"limits.at_least_one_entry_required": "최소 한 개의 항목이 필요해요.",
|
||||
"limits.base64_length_invalid": "Base64 문자열 길이는 {min}자에서 {maxLength}자 사이여야 해요.",
|
||||
"limits.bucket_required": "`bucket`은 필수 항목이에요.",
|
||||
"limits.cannot_shrink_reserved_slots": "예약된 슬롯을 줄일 수 없어요.",
|
||||
"limits.cannot_specify_before_and_after": "`before`와 `after`를 동시에 지정할 수 없어요.",
|
||||
"limits.content_exceeds_max_length": "텍스트는 {maxLength, plural, other {#자}}를 초과할 수 없어요.",
|
||||
"limits.integer_out_of_int64_range": "정수 값이 유효한 int64 범위를 벗어났어요.",
|
||||
"limits.rows_required": "`rows`는 필수 항목이에요.",
|
||||
"limits.string_length_exact": "문자열은 정확히 {length, plural, other {#자}}여야 해요.",
|
||||
"limits.string_length_invalid": "문자열 길이는 {min}자에서 {max}자 사이여야 해요.",
|
||||
"limits.synced_preferences_too_large": "동기화된 기본 설정이 {max_bytes}바이트를 초과했어요.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_LT_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Ištekliaus atnaujinti nepavyko. Pabandyk dar kartą.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Naudotojas turi būti botas, kad jį būtų galima pažymėti kaip sistemos naudotoją.",
|
||||
"age_verification.already_verified": "Amžiaus patvirtinimą jau atlikai.",
|
||||
"attachments_and_uploads.attachment_fields_required": "Reikalingi: `attachment_id`, `channel_id`, `message_id` ir `expires_at`.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` ir `message_id` turi būti galiojantys sveikieji skaičiai.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Priedas \"{filename}\" turi būti paveikslėlis (png, jpg, jpeg, webp arba gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Žinutėje nerastas priedas su ID {attachmentId}.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Priedai šiai žinutei neleidžiami.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_LT_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Žinutėje nerasta galiojančios medijos.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Peržiūra turi būti JPEG formato.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Nurodytas priedas \"{filename}\" nebuvo rastas žinutės prieduose.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` turi būti galiojantis sveikasis skaičius.",
|
||||
"attachments_and_uploads.too_many_files": "Per daug failų. Leidžiama daugiausia {maxFiles, plural, one {# failas} few {# failai} many {# failo} other {# failų}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Aptiktas neišspręstas `attachment://` URL. Jis turėjo būti išspręstas prieš apdorojant įterptuosius elementus.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Įkeltas priedas {filename} nerastas.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_LT_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Pasiekei {count, plural, one {# emoji} few {# emoji} many {# emoji} other {# emoji}} ribą.",
|
||||
"limits.at_least_one_entry_required": "Reikalingas bent vienas įrašas.",
|
||||
"limits.base64_length_invalid": "Base64 eilutės ilgis turi būti nuo {min} iki {maxLength} simbolių.",
|
||||
"limits.bucket_required": "`bucket` reikalingas.",
|
||||
"limits.cannot_shrink_reserved_slots": "Negali sumažinti rezervuotų vietų.",
|
||||
"limits.cannot_specify_before_and_after": "Negali nurodyti `before` ir `after` vienu metu.",
|
||||
"limits.content_exceeds_max_length": "Tekstas neturi viršyti {maxLength, plural, one {# simbolio} few {# simbolių} many {# simbolio} other {# simbolių}}.",
|
||||
"limits.integer_out_of_int64_range": "Sveikojo skaičiaus reikšmė yra už galiojančio int64 diapazono ribų.",
|
||||
"limits.rows_required": "`rows` reikalingas.",
|
||||
"limits.string_length_exact": "Eilutės ilgis turi būti lygiai {length, plural, one {# simbolis} few {# simboliai} many {# simbolio} other {# simbolių}}.",
|
||||
"limits.string_length_invalid": "Eilutės ilgis turi būti nuo {min} iki {max} simbolių.",
|
||||
"limits.synced_preferences_too_large": "Sinchronizuotų nuostatų dydis viršija {max_bytes} baitus.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_NL_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "We konden het item niet bijwerken. Probeer het opnieuw.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Gebruiker moet een bot zijn om als systeemgebruiker te worden gemarkeerd.",
|
||||
"age_verification.already_verified": "Je hebt de leeftijdsverificatie al voltooid.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` en `expires_at` zijn verplicht.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` en `message_id` moeten geldige gehele getallen zijn.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Bijlage \"{filename}\" moet een afbeeldingsbestand zijn (png, jpg, jpeg, webp of gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Bijlage met ID {attachmentId} is niet gevonden in het bericht.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Bijlagen zijn niet toegestaan voor dit bericht.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_NL_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Geen geldige media gevonden in het bericht.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Het voorbeeld moet het JPEG-formaat hebben.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "De bijlage \"{filename}\" waarnaar wordt verwezen, is niet gevonden in de bijlagen van het bericht.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` moet een geldig geheel getal zijn.",
|
||||
"attachments_and_uploads.too_many_files": "Te veel bestanden. Maximaal {maxFiles, plural, one {# bestand} other {# bestanden}} toegestaan.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Onopgeloste `attachment://`-URL gedetecteerd. Dit had moeten worden opgelost vóór het verwerken van de ingesloten inhoud.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "De geüploade bijlage {filename} is niet gevonden.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_NL_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Je hebt het maximum van {count, plural, one {# emoji} other {# emoji’s}} bereikt.",
|
||||
"limits.at_least_one_entry_required": "Minimaal één invoer is vereist.",
|
||||
"limits.base64_length_invalid": "De Base64-string moet tussen {min} en {maxLength} tekens lang zijn.",
|
||||
"limits.bucket_required": "`bucket` is vereist.",
|
||||
"limits.cannot_shrink_reserved_slots": "Je kunt het aantal gereserveerde slots niet verminderen.",
|
||||
"limits.cannot_specify_before_and_after": "Je kunt niet zowel `before` als `after` opgeven.",
|
||||
"limits.content_exceeds_max_length": "Tekst mag niet meer dan {maxLength, plural, one {# teken} other {# tekens}} bevatten.",
|
||||
"limits.integer_out_of_int64_range": "De waarde valt buiten het geldige int64-bereik.",
|
||||
"limits.rows_required": "`rows` is vereist.",
|
||||
"limits.string_length_exact": "De string moet precies {length, plural, one {# teken} other {# tekens}} bevatten.",
|
||||
"limits.string_length_invalid": "De string moet tussen {min} en {max} tekens lang zijn.",
|
||||
"limits.synced_preferences_too_large": "Gesynchroniseerde voorkeuren overschrijden {max_bytes} bytes.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_NO_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Vi kunne ikke oppdatere ressursen. Prøv på nytt.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Brukeren må være en bot for å bli merket som en systembruker.",
|
||||
"age_verification.already_verified": "Du har allerede fullført aldersverifisering.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` og `expires_at` er påkrevd.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` og `message_id` må være gyldige heltall.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Vedlegget «{filename}» må være en bildefil (png, jpg, jpeg, webp eller gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Vedlegget med ID {attachmentId} ble ikke funnet i meldingen.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Vedlegg er ikke tillatt for denne meldingen.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_NO_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Ingen gyldige medier ble funnet i meldingen.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Forhåndsvisningen må være i JPEG-format.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Vedlegget «{filename}» det refereres til, ble ikke funnet blant meldingsvedleggene.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` må være et gyldig heltall.",
|
||||
"attachments_and_uploads.too_many_files": "For mange filer. Maksimalt {maxFiles, plural, one {# fil} other {# filer}} er tillatt.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "En uløst `attachment://`-URL ble oppdaget. Denne skulle ha vært løst før behandling av innebygd innhold.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Det opplastede vedlegget {filename} ble ikke funnet.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_NO_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Du har nådd grensen på {count, plural, one {# emoji} other {# emojier}}.",
|
||||
"limits.at_least_one_entry_required": "Minst én oppføring er påkrevd.",
|
||||
"limits.base64_length_invalid": "Base64-strengen må inneholde mellom {min} og {maxLength} tegn.",
|
||||
"limits.bucket_required": "`bucket` er påkrevd.",
|
||||
"limits.cannot_shrink_reserved_slots": "Du kan ikke redusere antallet reserverte plasser.",
|
||||
"limits.cannot_specify_before_and_after": "Du kan ikke angi både `before` og `after`.",
|
||||
"limits.content_exceeds_max_length": "Teksten kan ikke overstige {maxLength, plural, one {# tegn} other {# tegn}}.",
|
||||
"limits.integer_out_of_int64_range": "Heltallsverdien er utenfor det gyldige int64-området.",
|
||||
"limits.rows_required": "`rows` er påkrevd.",
|
||||
"limits.string_length_exact": "Strengen må være nøyaktig {length, plural, one {# tegn} other {# tegn}} lang.",
|
||||
"limits.string_length_invalid": "Strengen må inneholde mellom {min} og {max} tegn.",
|
||||
"limits.synced_preferences_too_large": "Synkroniserte innstillinger overskrider {max_bytes} byte.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_PL_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Nie udało się zaktualizować zasobu. Spróbuj jeszcze raz.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Użytkownik musi być botem, aby mógł być oznaczony jako użytkownik systemowy.",
|
||||
"age_verification.already_verified": "Weryfikacja wieku została już zakończona.",
|
||||
"attachments_and_uploads.attachment_fields_required": "Wymagane: `attachment_id`, `channel_id`, `message_id` i `expires_at`.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` i `message_id` muszą być prawidłowymi liczbami całkowitymi.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Załącznik \"{filename}\" musi być plikiem graficznym (png, jpg, jpeg, webp lub gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Załącznika o ID {attachmentId} nie znaleziono w wiadomości.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Nie możesz dołączać załączników do tej wiadomości.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_PL_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Nie znaleziono prawidłowych multimediów w wiadomości.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Podgląd musi być w formacie JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Nie znaleziono załącznika \"{filename}\" w załącznikach wiadomości.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` musi być poprawną liczbą całkowitą.",
|
||||
"attachments_and_uploads.too_many_files": "Zbyt wiele plików. {maxFiles, plural, one {Dozwolony jest maksymalnie # plik.} few {Dozwolone są maksymalnie # pliki.} many {Dozwolonych jest maksymalnie # plików.} other {Dozwolone jest maksymalnie # pliku.}}",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Wykryto nierozwiązany adres URL `attachment://`. Powinien zostać rozwiązany przed przetwarzaniem elementów osadzonych.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Nie znaleziono przesłanego załącznika {filename}.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_PL_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Osiągnięto limit {count, plural, one {# emoji} few {# emoji} many {# emoji} other {# emoji}}.",
|
||||
"limits.at_least_one_entry_required": "Wymagany jest co najmniej jeden wpis.",
|
||||
"limits.base64_length_invalid": "Długość ciągu Base64 musi wynosić od {min} do {maxLength} znaków.",
|
||||
"limits.bucket_required": "`bucket` jest wymagany.",
|
||||
"limits.cannot_shrink_reserved_slots": "Nie możesz zmniejszyć liczby zarezerwowanych miejsc.",
|
||||
"limits.cannot_specify_before_and_after": "Nie możesz określić jednocześnie `before` i `after`.",
|
||||
"limits.content_exceeds_max_length": "Tekst nie może mieć więcej niż {maxLength, plural, one {# znak} few {# znaki} many {# znaków} other {# znaku}}.",
|
||||
"limits.integer_out_of_int64_range": "Wartość całkowita wykracza poza prawidłowy zakres int64.",
|
||||
"limits.rows_required": "`rows` jest wymagane.",
|
||||
"limits.string_length_exact": "Ciąg musi mieć dokładnie {length, plural, one {# znak} few {# znaki} many {# znaków} other {# znaku}}.",
|
||||
"limits.string_length_invalid": "Długość ciągu musi mieścić się w zakresie od {min} do {max} znaków.",
|
||||
"limits.synced_preferences_too_large": "Zsynchronizowane ustawienia przekraczają {max_bytes} bajtów.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_PT_BR_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Não foi possível atualizar o recurso. Tente de novo.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "O usuário deve ser um bot para ser marcado como usuário de sistema.",
|
||||
"age_verification.already_verified": "Você já concluiu a verificação de idade.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` e `expires_at` são obrigatórios.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` e `message_id` devem ser números inteiros válidos.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "O anexo \"{filename}\" deve ser um arquivo de imagem (png, jpg, jpeg, webp ou gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "O anexo com ID {attachmentId} não foi encontrado na mensagem.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Anexos não são permitidos para esta mensagem.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_PT_BR_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Nenhuma mídia válida foi encontrada na mensagem.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "A prévia deve estar no formato JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "O anexo referenciado \"{filename}\" não foi encontrado nos anexos da mensagem.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` deve ser um número inteiro válido.",
|
||||
"attachments_and_uploads.too_many_files": "Há arquivos demais. O limite é de {maxFiles, plural, =1 {# arquivo} other {# arquivos}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "URL `attachment://` não resolvida detectada. Isso deveria ter sido resolvido antes do processamento de incorporação.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "O anexo enviado {filename} não foi encontrado.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_PT_BR_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Você atingiu o máximo de {count, plural, =1 {# emoji} other {# emojis}}.",
|
||||
"limits.at_least_one_entry_required": "Pelo menos uma entrada é obrigatória.",
|
||||
"limits.base64_length_invalid": "O comprimento da string Base64 precisa estar entre {min} e {maxLength} caracteres.",
|
||||
"limits.bucket_required": "`bucket` é necessário.",
|
||||
"limits.cannot_shrink_reserved_slots": "Você não pode reduzir os slots reservados.",
|
||||
"limits.cannot_specify_before_and_after": "Você não pode especificar `before` e `after` ao mesmo tempo.",
|
||||
"limits.content_exceeds_max_length": "O texto não deve exceder {maxLength, plural, =1 {# caractere} other {# caracteres}}.",
|
||||
"limits.integer_out_of_int64_range": "O valor inteiro está fora do intervalo válido de int64.",
|
||||
"limits.rows_required": "`rows` é necessário.",
|
||||
"limits.string_length_exact": "A string precisa ter exatamente {length, plural, =0 {# caracteres} one {# caractere} many {# caracteres} other {# caracteres}}.",
|
||||
"limits.string_length_invalid": "O comprimento da string precisa estar entre {min} e {max} caracteres.",
|
||||
"limits.synced_preferences_too_large": "Preferências sincronizadas excedem {max_bytes} bytes.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_RO_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Resursa nu a putut fi actualizată. Încearcă din nou.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Utilizatorul trebuie să fie un bot pentru a fi marcat ca utilizator de sistem.",
|
||||
"age_verification.already_verified": "Ai finalizat deja verificarea vârstei.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` și `expires_at` sunt obligatorii.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` și `message_id` trebuie să fie numere întregi valide.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Atașamentul \"{filename}\" trebuie să fie un fișier de imagine (png, jpg, jpeg, webp sau gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Atașamentul cu ID-ul {attachmentId} nu a fost găsit în mesaj.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Atașamentele nu sunt permise pentru acest mesaj.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_RO_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Nu a fost găsit niciun conținut media valid în mesaj.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Previzualizarea trebuie să fie JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Atașamentul referențiat \"{filename}\" nu a fost găsit în atașamentele mesajului.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` trebuie să fie un număr întreg valid.",
|
||||
"attachments_and_uploads.too_many_files": "Prea multe fișiere. Sunt permise maximum {maxFiles, plural, one {# fișier} few {# fișiere} other {# de fișiere}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "S-a detectat o adresă URL `attachment://` nerezolvată. Aceasta ar fi trebuit rezolvată înainte de procesarea încorporărilor.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Atașamentul încărcat {filename} nu a fost găsit.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_RO_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Ai atins numărul maxim de {count, plural, one {# emoji} few {# emoji} other {# de emoji}}.",
|
||||
"limits.at_least_one_entry_required": "Este necesară cel puțin o intrare.",
|
||||
"limits.base64_length_invalid": "Lungimea șirului Base64 trebuie să fie între {min} și {maxLength} caractere.",
|
||||
"limits.bucket_required": "`bucket` este obligatoriu.",
|
||||
"limits.cannot_shrink_reserved_slots": "Nu poți reduce sloturile rezervate.",
|
||||
"limits.cannot_specify_before_and_after": "Nu poți specifica atât `before`, cât și `after`.",
|
||||
"limits.content_exceeds_max_length": "Textul nu trebuie să depășească {maxLength, plural, one {# caracter} few {# caractere} other {# de caractere}}.",
|
||||
"limits.integer_out_of_int64_range": "Valoarea întreagă este în afara intervalului valid pentru int64.",
|
||||
"limits.rows_required": "`rows` este obligatoriu.",
|
||||
"limits.string_length_exact": "Șirul trebuie să conțină exact {length, plural, one {# caracter} few {# caractere} other {# de caractere}}.",
|
||||
"limits.string_length_invalid": "Lungimea șirului trebuie să fie între {min} și {max} caractere.",
|
||||
"limits.synced_preferences_too_large": "Preferințele sincronizate depășesc {max_bytes} octeți.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_RU_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Не удалось обновить ресурс. Попробуй ещё раз.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Пользователь должен быть ботом, чтобы отметить его как системного пользователя.",
|
||||
"age_verification.already_verified": "Возраст уже подтверждён.",
|
||||
"attachments_and_uploads.attachment_fields_required": "Необходимо указать `attachment_id`, `channel_id`, `message_id` и `expires_at`.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` и `message_id` должны быть действительными целыми числами.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Вложение «{filename}» должно быть файлом изображения (png, jpg, jpeg, webp или gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Вложение с ID {attachmentId} не найдено в сообщении.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Вложения запрещены для этого сообщения.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_RU_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "В сообщении не найдено подходящих медиафайлов.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Превью должно быть в формате JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Указанное вложение «{filename}» не найдено среди вложений сообщения.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` должно быть действительным целым числом.",
|
||||
"attachments_and_uploads.too_many_files": "Слишком много файлов. Разрешено максимум {maxFiles} {maxFiles, plural, one {файл} few {файла} many {файлов} other {файла}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Обнаружен неразрешённый URL `attachment://`. Он должен был быть разрешён до обработки встраивания.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Не удалось найти загруженное вложение {filename}.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_RU_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Ты достиг максимума: {count} {count, plural, one {эмодзи} few {эмодзи} many {эмодзи} other {эмодзи}}.",
|
||||
"limits.at_least_one_entry_required": "Требуется хотя бы одна запись.",
|
||||
"limits.base64_length_invalid": "Длина строки Base64 должна быть от {min} до {maxLength} символов.",
|
||||
"limits.bucket_required": "Требуется `bucket`.",
|
||||
"limits.cannot_shrink_reserved_slots": "Нельзя уменьшить количество зарезервированных слотов.",
|
||||
"limits.cannot_specify_before_and_after": "Нельзя указывать `before` и `after` одновременно.",
|
||||
"limits.content_exceeds_max_length": "Текст не должен превышать {maxLength} {maxLength, plural, one {символ} few {символа} many {символов} other {символа}}.",
|
||||
"limits.integer_out_of_int64_range": "Целочисленное значение выходит за пределы допустимого диапазона int64.",
|
||||
"limits.rows_required": "Требуется `rows`.",
|
||||
"limits.string_length_exact": "Строка должна содержать ровно {length, plural, one {# символ} few {# символа} many {# символов} other {# символа}}.",
|
||||
"limits.string_length_invalid": "Длина строки должна быть от {min} до {max} символов.",
|
||||
"limits.synced_preferences_too_large": "Синхронизируемые настройки превышают {max_bytes} байт.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_SV_SE_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Det gick inte att uppdatera resursen. Försök igen.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Användaren måste vara en bot för att markeras som en systemanvändare.",
|
||||
"age_verification.already_verified": "Du har redan slutfört åldersverifieringen.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` och `expires_at` krävs.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` och `message_id` måste vara giltiga heltal.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Bilagan \"{filename}\" måste vara en bildfil (png, jpg, jpeg, webp eller gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Bilagan med ID {attachmentId} hittades inte i meddelandet.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Bilagor tillåts inte för detta meddelande.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_SV_SE_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Inget giltigt medieinnehåll hittades i meddelandet.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Förhandsgranskningsbilden måste vara i JPEG-format.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Den refererade bilagan \"{filename}\" hittades inte i meddelandets bilagor.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` måste vara ett giltigt heltal.",
|
||||
"attachments_and_uploads.too_many_files": "För många filer. Högst {maxFiles, plural, one {# fil} other {# filer}} tillåts.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "En olöst `attachment://`-URL upptäcktes. Detta skulle ha lösts innan inbäddningarna behandlades.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Den uppladdade bilagan {filename} hittades inte.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_SV_SE_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Du har nått gränsen på {count, plural, one {# emoji} other {# emojis}}.",
|
||||
"limits.at_least_one_entry_required": "Minst en post krävs.",
|
||||
"limits.base64_length_invalid": "Base64-strängens längd måste vara mellan {min} och {maxLength} tecken.",
|
||||
"limits.bucket_required": "`bucket` är obligatoriskt.",
|
||||
"limits.cannot_shrink_reserved_slots": "Du kan inte minska antalet reserverade platser.",
|
||||
"limits.cannot_specify_before_and_after": "Du kan inte ange både `before` och `after`.",
|
||||
"limits.content_exceeds_max_length": "Texten får inte innehålla fler än {maxLength, number} tecken.",
|
||||
"limits.integer_out_of_int64_range": "Heltalsvärdet ligger utanför det giltiga int64-intervallet.",
|
||||
"limits.rows_required": "`rows` är obligatoriskt.",
|
||||
"limits.string_length_exact": "Strängen måste vara exakt {length, plural, one {# tecken} other {# tecken}} lång.",
|
||||
"limits.string_length_invalid": "Stränglängden måste vara mellan {min} och {max} tecken.",
|
||||
"limits.synced_preferences_too_large": "Synkroniserade inställningar överskrider {max_bytes} byte.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_TH_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "ไม่สามารถอัปเดตทรัพยากรได้ โปรดลองอีกครั้ง",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "ผู้ใช้ต้องเป็นบอทจึงจะสามารถระบุว่าเป็นผู้ใช้ระบบได้",
|
||||
"age_verification.already_verified": "คุณยืนยันอายุเรียบร้อยแล้ว",
|
||||
"attachments_and_uploads.attachment_fields_required": "ต้องระบุ `attachment_id`, `channel_id`, `message_id` และ `expires_at`",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` และ `message_id` ต้องเป็นจำนวนเต็มที่ถูกต้อง",
|
||||
"attachments_and_uploads.attachment_must_be_image": "ไฟล์แนบ \"{filename}\" ต้องเป็นไฟล์รูปภาพ (png, jpg, jpeg, webp หรือ gif)",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "ไม่พบไฟล์แนบ ID {attachmentId} ในข้อความ",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "ข้อความนี้ไม่อนุญาตให้แนบไฟล์",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_TH_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "ไม่พบสื่อที่ถูกต้องในข้อความ",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "ภาพตัวอย่างต้องเป็นไฟล์ JPEG",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "ไม่พบไฟล์แนบที่อ้างอิง \"{filename}\" ในไฟล์แนบของข้อความ",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` ต้องเป็นจำนวนเต็มที่ถูกต้อง",
|
||||
"attachments_and_uploads.too_many_files": "ไฟล์มากเกินไป อนุญาตสูงสุด {maxFiles, plural, other {# ไฟล์}}",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "ตรวจพบ URL `attachment://` ที่ยังไม่ได้แปลงค่า ควรแปลงค่าก่อนประมวลผลเอมเบด",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "ไม่พบไฟล์แนบที่อัปโหลด {filename}",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_TH_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "คุณใช้อิโมจิครบ {count} {count, plural, other {ตัว}} แล้ว",
|
||||
"limits.at_least_one_entry_required": "ต้องมีอย่างน้อยหนึ่งรายการ",
|
||||
"limits.base64_length_invalid": "ความยาวสตริง Base64 ต้องอยู่ระหว่าง {min} ถึง {maxLength} ตัวอักษร",
|
||||
"limits.bucket_required": "ต้องระบุ `bucket`",
|
||||
"limits.cannot_shrink_reserved_slots": "คุณไม่สามารถลดจำนวนช่องที่จองไว้ได้",
|
||||
"limits.cannot_specify_before_and_after": "คุณไม่สามารถระบุทั้ง `before` และ `after` ได้",
|
||||
"limits.content_exceeds_max_length": "ข้อความต้องไม่เกิน {maxLength} {maxLength, plural, other {ตัวอักษร}}",
|
||||
"limits.integer_out_of_int64_range": "ค่าจำนวนเต็มอยู่นอกช่วง int64 ที่ถูกต้อง",
|
||||
"limits.rows_required": "ต้องระบุ `rows`",
|
||||
"limits.string_length_exact": "สตริงต้องมีความยาว {length, plural, other {# ตัวอักษร}}พอดี",
|
||||
"limits.string_length_invalid": "ความยาวสตริงต้องอยู่ระหว่าง {min} ถึง {max} ตัวอักษร",
|
||||
"limits.synced_preferences_too_large": "การตั้งค่าที่ซิงค์มีขนาดเกิน {max_bytes} ไบต์",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_TR_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Kaynağı güncelleyemedik. Tekrar dene.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Kullanıcı, sistem kullanıcısı olarak işaretlenmek için bot olmalı.",
|
||||
"age_verification.already_verified": "Yaş doğrulamasını zaten tamamladın.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` ve `expires_at` gerekli.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` ve `message_id` geçerli tam sayı olmalı.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "\"{filename}\" eki görsel bir dosya olmalı (png, jpg, jpeg, webp veya gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Kimliği {attachmentId} olan ek mesajda bulunamadı.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Bu mesaja ek ekleyemezsin.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_TR_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Mesajda geçerli medya yok.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Önizleme JPEG olmalı.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Referans gösterilen \"{filename}\" eki mesaj eklerinde bulunamadı.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` geçerli bir tam sayı olmalı.",
|
||||
"attachments_and_uploads.too_many_files": "Çok fazla dosya. En fazla {maxFiles, plural, one {# dosya} other {# dosya}} yükleyebilirsin.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Çözülmemiş `attachment://` URL'si algılandı. Bu, gömülü içerik işlenmeden önce çözülmeliydi.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Yüklenen {filename} eki bulunamadı.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_TR_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "{count, plural, one {# emoji} other {# emoji}} sınırına ulaştın.",
|
||||
"limits.at_least_one_entry_required": "En az bir öğe gerekli.",
|
||||
"limits.base64_length_invalid": "Base64 dize uzunluğu {min} ile {maxLength} karakter arasında olmalı.",
|
||||
"limits.bucket_required": "`bucket` gerekli.",
|
||||
"limits.cannot_shrink_reserved_slots": "Ayrılmış yuvaları küçültemezsin.",
|
||||
"limits.cannot_specify_before_and_after": "Hem `before` hem de `after` belirtemezsin.",
|
||||
"limits.content_exceeds_max_length": "Metin {maxLength, plural, one {# karakteri} other {# karakteri}} geçemez.",
|
||||
"limits.integer_out_of_int64_range": "Tam sayı değeri geçerli int64 aralığında değil.",
|
||||
"limits.rows_required": "`rows` gerekli.",
|
||||
"limits.string_length_exact": "Dize tam olarak {length, plural, one {# karakter} other {# karakter}} olmalı.",
|
||||
"limits.string_length_invalid": "Dize uzunluğu {min} ile {max} karakter arasında olmalı.",
|
||||
"limits.synced_preferences_too_large": "Senkronize edilen tercihler {max_bytes} baytı aşıyor.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_UK_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Не вдалося оновити ресурс. Спробуй ще раз.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Користувач має бути ботом, щоб його можна було позначити як системного користувача.",
|
||||
"age_verification.already_verified": "Перевірку віку вже пройдено.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` та `expires_at` є обов'язковими.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` та `message_id` мають бути дійсними цілими числами.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Вкладення \"{filename}\" має бути файлом зображення (png, jpg, jpeg, webp або gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Вкладення з ID {attachmentId} не знайдено в повідомленні.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Для цього повідомлення вкладення заборонені.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_UK_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "У повідомленні не знайдено коректних медіафайлів.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Попередній перегляд має бути у форматі JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Вказане вкладення \"{filename}\" не знайдено у вкладеннях повідомлення.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` має бути коректним цілим числом.",
|
||||
"attachments_and_uploads.too_many_files": "Забагато файлів. Дозволено максимум {maxFiles} {maxFiles, plural, one {файл} few {файли} many {файлів} other {файла}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Виявлено нерозв'язану URL-адресу `attachment://`. Її потрібно було розв'язати до обробки ембедів.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Завантажене вкладення {filename} не знайдено.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_UK_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Досягнуто максимуму: {count} {count, plural, one {емодзі} few {емодзі} many {емодзі} other {емодзі}}.",
|
||||
"limits.at_least_one_entry_required": "Потрібен принаймні один запис.",
|
||||
"limits.base64_length_invalid": "Довжина рядка Base64 має бути від {min} до {maxLength} символів.",
|
||||
"limits.bucket_required": "`bucket` обов'язковий.",
|
||||
"limits.cannot_shrink_reserved_slots": "Ти не можеш зменшити кількість зарезервованих слотів.",
|
||||
"limits.cannot_specify_before_and_after": "Ти не можеш вказати одночасно `before` та `after`.",
|
||||
"limits.content_exceeds_max_length": "Текст не повинен перевищувати {maxLength} {maxLength, plural, one {символ} few {символи} many {символів} other {символа}}.",
|
||||
"limits.integer_out_of_int64_range": "Ціле значення виходить за межі допустимого діапазону int64.",
|
||||
"limits.rows_required": "`rows` обов'язковий.",
|
||||
"limits.string_length_exact": "Рядок має містити рівно {length, plural, one {# символ} few {# символи} many {# символів} other {# символа}}.",
|
||||
"limits.string_length_invalid": "Довжина рядка має бути від {min} до {max} символів.",
|
||||
"limits.synced_preferences_too_large": "Синхронізовані налаштування перевищують {max_bytes} байт.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_VI_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "Không thể cập nhật tài nguyên. Vui lòng thử lại.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Người dùng phải là bot mới được đánh dấu là người dùng hệ thống.",
|
||||
"age_verification.already_verified": "Bạn đã hoàn tất xác minh tuổi.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` và `expires_at` là bắt buộc.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` và `message_id` phải là các số nguyên hợp lệ.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Tệp đính kèm \"{filename}\" phải là tệp hình ảnh (png, jpg, jpeg, webp hoặc gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Không tìm thấy tệp đính kèm có ID {attachmentId} trong tin nhắn.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Tin nhắn này không cho phép tệp đính kèm.",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_VI_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Không tìm thấy media hợp lệ nào trong tin nhắn.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Ảnh xem trước phải ở định dạng JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Không tìm thấy tệp đính kèm tham chiếu \"{filename}\" trong tệp đính kèm của tin nhắn.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` phải là số nguyên hợp lệ.",
|
||||
"attachments_and_uploads.too_many_files": "Quá nhiều tệp. Chỉ cho phép tối đa {maxFiles} {maxFiles, plural, other {tệp}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Phát hiện URL `attachment://` chưa được phân giải. URL này lẽ ra phải được phân giải trước khi xử lý embed.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Không tìm thấy tệp đính kèm đã tải lên {filename}.",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_VI_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "Bạn đã đạt tối đa {count} {count, plural, other {emoji}}.",
|
||||
"limits.at_least_one_entry_required": "Cần ít nhất một mục.",
|
||||
"limits.base64_length_invalid": "Độ dài chuỗi Base64 phải từ {min} đến {maxLength} ký tự.",
|
||||
"limits.bucket_required": "`bucket` là bắt buộc.",
|
||||
"limits.cannot_shrink_reserved_slots": "Bạn không thể giảm số lượng vị trí đã đặt trước.",
|
||||
"limits.cannot_specify_before_and_after": "Bạn không thể chỉ định đồng thời `before` và `after`.",
|
||||
"limits.content_exceeds_max_length": "Văn bản không được dài quá {maxLength} {maxLength, plural, other {ký tự}}.",
|
||||
"limits.integer_out_of_int64_range": "Giá trị số nguyên nằm ngoài phạm vi int64 hợp lệ.",
|
||||
"limits.rows_required": "`rows` là bắt buộc.",
|
||||
"limits.string_length_exact": "Chuỗi phải có đúng {length, plural, other {# ký tự}}.",
|
||||
"limits.string_length_invalid": "Độ dài chuỗi phải từ {min} đến {max} ký tự.",
|
||||
"limits.synced_preferences_too_large": "Tùy chọn đồng bộ hóa vượt quá {max_bytes} byte.",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_ZH_CN_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "无法更新资源。请重试。",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "用户必须是机器人才能被标记为系统用户。",
|
||||
"age_verification.already_verified": "你已完成年龄验证。",
|
||||
"attachments_and_uploads.attachment_fields_required": "需要提供 `attachment_id`、`channel_id`、`message_id` 和 `expires_at`。",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`、`channel_id` 和 `message_id` 必须是有效的整数。",
|
||||
"attachments_and_uploads.attachment_must_be_image": "附件“{filename}”必须是图片文件(png、jpg、jpeg、webp 或 gif)。",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "消息中未找到 ID 为 {attachmentId} 的附件。",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "此消息不允许添加附件。",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_ZH_CN_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "消息中未发现有效媒体。",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "预览必须是 JPEG。",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "消息附件中未找到引用的附件“{filename}”。",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` 必须为有效整数。",
|
||||
"attachments_and_uploads.too_many_files": "文件过多。最多允许 {maxFiles, plural, other {# 个文件}}。",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "检测到未解析的 `attachment://` URL。此 URL 应在嵌入处理前解析。",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "未找到上传的附件 {filename}。",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_ZH_CN_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "你已达到 {count, plural, other {# 个表情}}的上限。",
|
||||
"limits.at_least_one_entry_required": "至少需要一个条目。",
|
||||
"limits.base64_length_invalid": "Base64 字符串长度必须在 {min} 到 {maxLength} 个字符之间。",
|
||||
"limits.bucket_required": "需要提供 `bucket`。",
|
||||
"limits.cannot_shrink_reserved_slots": "你不能减少预留槽位数量。",
|
||||
"limits.cannot_specify_before_and_after": "你不能同时指定 `before` 和 `after`。",
|
||||
"limits.content_exceeds_max_length": "文本不能超过 {maxLength, plural, other {# 个字符}}。",
|
||||
"limits.integer_out_of_int64_range": "整数值超出有效的 int64 范围。",
|
||||
"limits.rows_required": "需要提供 `rows`。",
|
||||
"limits.string_length_exact": "字符串必须正好包含 {length, plural, other {# 个字符}}。",
|
||||
"limits.string_length_invalid": "字符串长度必须在 {min} 到 {max} 个字符之间。",
|
||||
"limits.synced_preferences_too_large": "同步偏好设置已超出 {max_bytes} 字节。",
|
||||
|
||||
@@ -44,8 +44,6 @@ const ERROR_I18N_ZH_TW_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"admin_and_system.update_failed": "我們無法更新資源。請再試一次。",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "使用者必須是機器人才能標記為系統使用者。",
|
||||
"age_verification.already_verified": "你已完成年齡驗證。",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`、`channel_id`、`message_id` 和 `expires_at` 為必填。",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`、`channel_id` 和 `message_id` 必須為有效的整數。",
|
||||
"attachments_and_uploads.attachment_must_be_image": "附件「{filename}」必須是圖片檔案(png、jpg、jpeg、webp 或 gif)。",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "在訊息中找不到 ID 為 {attachmentId} 的附件。",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "此訊息不允許附件。",
|
||||
@@ -70,7 +68,6 @@ const ERROR_I18N_ZH_TW_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"attachments_and_uploads.no_valid_media_in_message": "在訊息中找不到有效的媒體。",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "預覽必須是 JPEG 格式。",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "在訊息附件中找不到參照的附件「{filename}」。",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` 必須是有效的整數。",
|
||||
"attachments_and_uploads.too_many_files": "檔案過多。最多允許 {maxFiles, plural, other {# 個檔案}}。",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "偵測到未解析的 `attachment://` URL。這應該在嵌入處理之前完成解析。",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "找不到已上傳的附件 {filename}。",
|
||||
@@ -300,12 +297,10 @@ const ERROR_I18N_ZH_TW_MESSAGES = defineErrorI18nLocaleMessages({
|
||||
"invites_and_packs.max_emojis_reached": "你已達到 {count, plural, other {# 個表情符號}}的上限。",
|
||||
"limits.at_least_one_entry_required": "至少需要一個項目。",
|
||||
"limits.base64_length_invalid": "Base64 字串長度必須介於 {min} 到 {maxLength} 個字元之間。",
|
||||
"limits.bucket_required": "需要 `bucket`。",
|
||||
"limits.cannot_shrink_reserved_slots": "你無法減少已保留的名額。",
|
||||
"limits.cannot_specify_before_and_after": "你無法同時指定 `before` 和 `after`。",
|
||||
"limits.content_exceeds_max_length": "文字不得超過 {maxLength, plural, other {# 個字元}}。",
|
||||
"limits.integer_out_of_int64_range": "整數值超出有效的 int64 範圍。",
|
||||
"limits.rows_required": "需要 `rows`。",
|
||||
"limits.string_length_exact": "字串必須正好包含 {length, plural, other {# 個字元}}。",
|
||||
"limits.string_length_invalid": "字串長度必須介於 {min} 到 {max} 個字元之間。",
|
||||
"limits.synced_preferences_too_large": "同步的偏好設定超過 {max_bytes} 位元組。",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "لم نتمكن من تحديث المورد. حاول مجددًا.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "يجب أن يكون المستخدم بوتًا ليتم تحديده كمستخدم نظام.",
|
||||
"age_verification.already_verified": "لقد أكملت التحقق من العمر بالفعل.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id` و`channel_id` و`message_id` و`expires_at` مطلوبة.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "يجب أن تكون `attachment_id` و`channel_id` و`message_id` أعدادًا صحيحة صالحة.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "يجب أن يكون المرفق \"{filename}\" ملف صورة (png أو jpg أو jpeg أو webp أو gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "لم يتم العثور على المرفق بالمعرف {attachmentId} في الرسالة.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "المرفقات غير مسموح بها لهذه الرسالة.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "لم يتم العثور على وسائط صالحة في الرسالة.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "يجب أن تكون المعاينة بصيغة JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "لم يتم العثور على المرفق المشار إليه \"{filename}\" في مرفقات الرسالة.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "يجب أن يكون `size_bytes` عددًا صحيحًا صالحًا.",
|
||||
"attachments_and_uploads.too_many_files": "عدد كبير جدًا من الملفات. الحد الأقصى المسموح به هو {maxFiles, plural, zero {# ملف} one {ملف واحد} two {ملفان} few {# ملفات} many {# ملفًا} other {# ملف}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "تم اكتشاف عنوان URL بصيغة `attachment://` لم يتم حله. كان ينبغي حله قبل معالجة التضمين.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "لم يتم العثور على المرفق المرفوع {filename}.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "لقد وصلت إلى الحد الأقصى وهو {count, plural, zero {# إيموجي} one {إيموجي واحد} two {إيموجيان} few {# إيموجيات} many {# إيموجي} other {# إيموجي}}.",
|
||||
"limits.at_least_one_entry_required": "مطلوب إدخال واحد على الأقل.",
|
||||
"limits.base64_length_invalid": "يجب أن يتراوح طول سلسلة Base64 بين {min} و{maxLength} حرفًا.",
|
||||
"limits.bucket_required": "`bucket` مطلوب.",
|
||||
"limits.cannot_shrink_reserved_slots": "لا يمكنك تقليص الخانات المحجوزة.",
|
||||
"limits.cannot_specify_before_and_after": "لا يمكنك تحديد `before` و`after` معًا.",
|
||||
"limits.content_exceeds_max_length": "يجب ألا يتجاوز طول النص {maxLength, plural, zero {# حرف} one {حرفًا واحدًا} two {حرفين} few {# أحرف} many {# حرفًا} other {# حرف}}.",
|
||||
"limits.integer_out_of_int64_range": "قيمة العدد الصحيح خارج النطاق المسموح به لـint64.",
|
||||
"limits.rows_required": "`rows` مطلوب.",
|
||||
"limits.string_length_exact": "يجب أن تحتوي السلسلة على {length, plural, zero {# حرف} one {حرف واحد} two {حرفين} few {# أحرف} many {# حرفًا} other {# حرف}} بالضبط.",
|
||||
"limits.string_length_invalid": "يجب أن يكون طول السلسلة بين {min} و{max} حرفًا.",
|
||||
"limits.synced_preferences_too_large": "تتجاوز التفضيلات المتزامنة الحد الأقصى وهو {max_bytes} بايت.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Не успяхме да актуализираме ресурса. Опитай отново.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Потребителят трябва да бъде бот, за да бъде маркиран като системен потребител.",
|
||||
"age_verification.already_verified": "Вече премина проверката за възраст.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` и `expires_at` са задължителни.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` и `message_id` трябва да са валидни цели числа.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Прикаченият файл \"{filename}\" трябва да е изображение (png, jpg, jpeg, webp или gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Прикаченият файл с ID {attachmentId} не беше намерен в съобщението.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "За това съобщение не са разрешени прикачени файлове.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Не е намерена валидна медия в съобщението.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Изображението за преглед трябва да е JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Посоченият прикачен файл \"{filename}\" не е намерен в прикачените файлове на съобщението.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` трябва да е валидно цяло число.",
|
||||
"attachments_and_uploads.too_many_files": "Твърде много файлове. Разрешени са максимум {maxFiles} {maxFiles, plural, one {файл} other {файла}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Открит е неразрешен `attachment://` URL. Това трябваше да бъде разрешено преди обработката на вградените елементи.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Каченият прикачен файл {filename} не е намерен.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Достигна максимума от {count} {count, plural, one {емоджи} other {емоджита}}.",
|
||||
"limits.at_least_one_entry_required": "Необходим е поне един запис.",
|
||||
"limits.base64_length_invalid": "Дължината на Base64 низа трябва да е между {min} и {maxLength} знака.",
|
||||
"limits.bucket_required": "Изисква се `bucket`.",
|
||||
"limits.cannot_shrink_reserved_slots": "Не можеш да намалиш броя на запазените слотове.",
|
||||
"limits.cannot_specify_before_and_after": "Не можеш да зададеш едновременно `before` и `after`.",
|
||||
"limits.content_exceeds_max_length": "Текстът не може да надвишава {maxLength} {maxLength, plural, one {знак} other {знака}}.",
|
||||
"limits.integer_out_of_int64_range": "Стойността е извън допустимия диапазон за int64.",
|
||||
"limits.rows_required": "Изискват се `rows`.",
|
||||
"limits.string_length_exact": "Низът трябва да съдържа точно {length} {length, plural, one {знак} other {знака}}.",
|
||||
"limits.string_length_invalid": "Низът трябва да е с дължина между {min} и {max} знака.",
|
||||
"limits.synced_preferences_too_large": "Синхронизираните настройки надвишават {max_bytes} байта.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Nepodařilo se aktualizovat zdroj. Zkuste to znovu.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Uživatel musí být bot, aby mohl být označen jako systémový uživatel.",
|
||||
"age_verification.already_verified": "Ověření věku už bylo dokončeno.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` a `expires_at` jsou povinné.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` a `message_id` musí být platné celočíselné hodnoty.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Příloha „{filename}“ musí být obrázkový soubor (png, jpg, jpeg, webp nebo gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Příloha s ID {attachmentId} se ve zprávě nenašla.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Pro tuto zprávu nejsou povoleny přílohy.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Ve zprávě nebylo nalezeno žádné platné médium.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Náhled musí být ve formátu JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Odkazovaná příloha \"{filename}\" nebyla nalezena mezi přílohami zprávy.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` musí být platné celé číslo.",
|
||||
"attachments_and_uploads.too_many_files": "Příliš mnoho souborů. Lze nahrát nejvýše {maxFiles, plural, one {# soubor} few {# soubory} many {# souboru} other {# souborů}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Nalezena nevyřešená adresa URL `attachment://`. Odkaz měl být vyřešen před zpracováním vloženého obsahu.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Nahraná příloha {filename} nebyla nalezena.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Dosáhli jste limitu {count, plural, one {# emoji} few {# emoji} many {# emoji} other {# emoji}}.",
|
||||
"limits.at_least_one_entry_required": "Je vyžadován alespoň jeden záznam.",
|
||||
"limits.base64_length_invalid": "Řetězec Base64 musí mít {min} až {maxLength} znaků.",
|
||||
"limits.bucket_required": "`bucket` je povinný.",
|
||||
"limits.cannot_shrink_reserved_slots": "Nemůžete snížit počet rezervovaných slotů.",
|
||||
"limits.cannot_specify_before_and_after": "Nemůžete zadat `before` i `after` zároveň.",
|
||||
"limits.content_exceeds_max_length": "Text nesmí přesáhnout {maxLength, plural, one {# znak} few {# znaky} many {# znaku} other {# znaků}}.",
|
||||
"limits.integer_out_of_int64_range": "Celé číslo je mimo platný rozsah int64.",
|
||||
"limits.rows_required": "`rows` je povinný.",
|
||||
"limits.string_length_exact": "Řetězec musí mít přesně {length, plural, one {# znak} few {# znaky} many {# znaku} other {# znaků}}.",
|
||||
"limits.string_length_invalid": "Řetězec musí mít {min} až {max} znaků.",
|
||||
"limits.synced_preferences_too_large": "Synchronizované předvolby přesahují {max_bytes} bajtů.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Ressourcen kunne ikke opdateres. Prøv igen.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Brugeren skal være en bot for at kunne markeres som systembruger.",
|
||||
"age_verification.already_verified": "Du har allerede gennemført aldersbekræftelse.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` og `expires_at` er påkrævet.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` og `message_id` skal være gyldige heltal.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Den vedhæftede fil \"{filename}\" skal være en billedfil (png, jpg, jpeg, webp eller gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Den vedhæftede fil med id {attachmentId} blev ikke fundet i beskeden.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Vedhæftede filer er ikke tilladt for denne besked.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Ingen gyldige medier blev fundet i beskeden.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Forhåndsvisningen skal være i JPEG-format.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Den refererede vedhæftede fil \"{filename}\" blev ikke fundet i beskedens vedhæftede filer.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` skal være et gyldigt heltal.",
|
||||
"attachments_and_uploads.too_many_files": "For mange filer. Maksimalt {maxFiles} {maxFiles, plural, one {fil} other {filer}} tilladt.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Der blev fundet en uafklaret `attachment://`-URL. Den skulle have været afklaret før behandlingen af indlejringer.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Den uploadede vedhæftede fil {filename} blev ikke fundet.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Du har nået grænsen på {count, plural, one {# emoji} other {# emojis}}.",
|
||||
"limits.at_least_one_entry_required": "Mindst ét element er nødvendigt.",
|
||||
"limits.base64_length_invalid": "Base64-strengens længde skal være mellem {min} og {maxLength} tegn.",
|
||||
"limits.bucket_required": "`bucket` er påkrævet.",
|
||||
"limits.cannot_shrink_reserved_slots": "Du kan ikke reducere antallet af reserverede pladser.",
|
||||
"limits.cannot_specify_before_and_after": "Du kan ikke bruge både `before` og `after`.",
|
||||
"limits.content_exceeds_max_length": "Teksten må ikke overstige {maxLength} {maxLength, plural, one {tegn} other {tegn}}.",
|
||||
"limits.integer_out_of_int64_range": "Heltalsværdien er uden for det gyldige int64-område.",
|
||||
"limits.rows_required": "`rows` er påkrævet.",
|
||||
"limits.string_length_exact": "Strengen skal være nøjagtig {length, plural, one {# tegn} other {# tegn}} lang.",
|
||||
"limits.string_length_invalid": "Strenglængden skal være mellem {min} og {max} tegn.",
|
||||
"limits.synced_preferences_too_large": "Synkroniserede indstillinger overskrider {max_bytes} bytes.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Wir konnten die Ressource nicht aktualisieren. Bitte versuche es erneut.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Ein Benutzer muss ein Bot sein, um als Systembenutzer markiert zu werden.",
|
||||
"age_verification.already_verified": "Du hast die Altersbestätigung bereits abgeschlossen.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` und `expires_at` sind erforderlich.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` und `message_id` müssen gültige Ganzzahlen sein.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Der Anhang „{filename}“ muss eine Bilddatei sein (png, jpg, jpeg, webp oder gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Anhang mit der ID {attachmentId} wurde nicht in der Nachricht gefunden.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Anhänge sind für diese Nachricht nicht erlaubt.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Es wurde kein gültiges Medium in der Nachricht gefunden.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Die Vorschau muss im JPEG-Format vorliegen.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Der referenzierte Anhang \"{filename}\" wurde in den Nachrichtenanhängen nicht gefunden.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` muss eine gültige Ganzzahl sein.",
|
||||
"attachments_and_uploads.too_many_files": "Zu viele Dateien. Maximal {maxFiles, plural, one {# Datei} other {# Dateien}} erlaubt.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Unaufgelöste `attachment://`-URL erkannt. Sie sollte vor der Einbettungsverarbeitung aufgelöst werden.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Der hochgeladene Anhang {filename} wurde nicht gefunden.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Du hast die maximale Anzahl von {count, plural, one {# Emoji} other {# Emojis}} erreicht.",
|
||||
"limits.at_least_one_entry_required": "Mindestens ein Eintrag ist erforderlich.",
|
||||
"limits.base64_length_invalid": "Die Länge der Base64-Zeichenkette muss zwischen {min} und {maxLength} Zeichen liegen.",
|
||||
"limits.bucket_required": "`bucket` ist erforderlich.",
|
||||
"limits.cannot_shrink_reserved_slots": "Du kannst reservierte Slots nicht verkleinern.",
|
||||
"limits.cannot_specify_before_and_after": "Du kannst nicht gleichzeitig `before` und `after` angeben.",
|
||||
"limits.content_exceeds_max_length": "Text darf maximal {maxLength, plural, one {# Zeichen} other {# Zeichen}} lang sein.",
|
||||
"limits.integer_out_of_int64_range": "Der Ganzzahlwert liegt außerhalb des gültigen int64-Bereichs.",
|
||||
"limits.rows_required": "`rows` ist erforderlich.",
|
||||
"limits.string_length_exact": "Die Zeichenkette muss genau {length, plural, one {# Zeichen} other {# Zeichen}} lang sein.",
|
||||
"limits.string_length_invalid": "Die Länge der Zeichenkette muss zwischen {min} und {max} Zeichen liegen.",
|
||||
"limits.synced_preferences_too_large": "Synchronisierte Einstellungen überschreiten {max_bytes} Bytes.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Δεν μπορέσαμε να ενημερώσουμε τον πόρο. Δοκίμασε ξανά.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Ο χρήστης πρέπει να είναι bot για να οριστεί ως χρήστης συστήματος.",
|
||||
"age_verification.already_verified": "Έχεις ήδη ολοκληρώσει την επαλήθευση ηλικίας.",
|
||||
"attachments_and_uploads.attachment_fields_required": "Τα `attachment_id`, `channel_id`, `message_id` και `expires_at` είναι υποχρεωτικά.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "Τα `attachment_id`, `channel_id` και `message_id` πρέπει να είναι έγκυροι ακέραιοι αριθμοί.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Το συνημμένο \"{filename}\" πρέπει να είναι αρχείο εικόνας (png, jpg, jpeg, webp ή gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Το συνημμένο με αναγνωριστικό {attachmentId} δεν βρέθηκε στο μήνυμα.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Δεν επιτρέπονται συνημμένα σε αυτό το μήνυμα.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Δεν βρέθηκαν έγκυρα πολυμέσα στο μήνυμα.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Η προεπισκόπηση πρέπει να είναι JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Το αναφερόμενο συνημμένο \"{filename}\" δεν βρέθηκε στα συνημμένα του μηνύματος.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "Το `size_bytes` πρέπει να είναι ένας έγκυρος ακέραιος αριθμός.",
|
||||
"attachments_and_uploads.too_many_files": "Πάρα πολλά αρχεία. Επιτρέπονται έως και {maxFiles, plural, one {# αρχείο} other {# αρχεία}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Βρέθηκε ανεπίλυτη διεύθυνση URL `attachment://`. Θα έπρεπε να είχε επιλυθεί πριν από την επεξεργασία των ενσωματώσεων.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Το ανεβασμένο συνημμένο {filename} δεν βρέθηκε.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Έχεις φτάσει το μέγιστο των {count} {count, plural, one {emoji} other {emoji}}.",
|
||||
"limits.at_least_one_entry_required": "Απαιτείται τουλάχιστον μία εγγραφή.",
|
||||
"limits.base64_length_invalid": "Το μήκος της συμβολοσειράς Base64 πρέπει να είναι μεταξύ {min} και {maxLength} χαρακτήρων.",
|
||||
"limits.bucket_required": "Το `bucket` απαιτείται.",
|
||||
"limits.cannot_shrink_reserved_slots": "Δεν μπορείς να μειώσεις τον αριθμό των δεσμευμένων θέσεων.",
|
||||
"limits.cannot_specify_before_and_after": "Δεν μπορείς να καθορίσεις ταυτόχρονα `before` και `after`.",
|
||||
"limits.content_exceeds_max_length": "Το κείμενο δεν πρέπει να υπερβαίνει τους {maxLength} {maxLength, plural, one {χαρακτήρα} other {χαρακτήρες}}.",
|
||||
"limits.integer_out_of_int64_range": "Η ακέραια τιμή είναι εκτός του έγκυρου εύρους int64.",
|
||||
"limits.rows_required": "Το `rows` απαιτείται.",
|
||||
"limits.string_length_exact": "Η συμβολοσειρά πρέπει να έχει ακριβώς {length, plural, one {# χαρακτήρα} other {# χαρακτήρες}}.",
|
||||
"limits.string_length_invalid": "Το μήκος της συμβολοσειράς πρέπει να είναι μεταξύ {min} και {max} χαρακτήρων.",
|
||||
"limits.synced_preferences_too_large": "Οι συγχρονισμένες προτιμήσεις υπερβαίνουν τα {max_bytes} bytes.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "We couldn't update the resource. Please try again.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "User must be a bot to be marked as a system user.",
|
||||
"age_verification.already_verified": "You've already completed age verification.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id`, and `expires_at` are required.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id`, and `message_id` must be valid integers.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Attachment \"{filename}\" must be an image file (png, jpg, jpeg, webp, or gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Attachment with ID {attachmentId} wasn't found in the message.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Attachments aren't allowed for this message.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "No valid media was found in the message.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Preview must be JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Referenced attachment \"{filename}\" wasn't found in message attachments.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` must be a valid integer.",
|
||||
"attachments_and_uploads.too_many_files": "Too many files. Maximum {maxFiles, plural, one {# file} other {# files}} allowed.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Unresolved `attachment://` URL detected. This should have been resolved before embed processing.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Uploaded attachment {filename} wasn't found.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "You've reached the maximum of {count, plural, one {# emoji} other {# emojis}}.",
|
||||
"limits.at_least_one_entry_required": "At least one entry is required.",
|
||||
"limits.base64_length_invalid": "Base64 string length must be between {min} and {maxLength} characters.",
|
||||
"limits.bucket_required": "`bucket` is required.",
|
||||
"limits.cannot_shrink_reserved_slots": "You can't shrink reserved slots.",
|
||||
"limits.cannot_specify_before_and_after": "You can't specify both `before` and `after`.",
|
||||
"limits.content_exceeds_max_length": "Text must not exceed {maxLength, plural, one {# character} other {# characters}}.",
|
||||
"limits.integer_out_of_int64_range": "Integer value is out of the valid int64 range.",
|
||||
"limits.rows_required": "`rows` is required.",
|
||||
"limits.string_length_exact": "String must be exactly {length, plural, one {# character} other {# characters}}.",
|
||||
"limits.string_length_invalid": "String length must be between {min} and {max} characters.",
|
||||
"limits.synced_preferences_too_large": "Synced preferences exceed {max_bytes} bytes.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "No se pudo actualizar el recurso. Inténtalo de nuevo.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "El usuario debe ser un bot para ser marcado como usuario del sistema.",
|
||||
"age_verification.already_verified": "Ya completaste la verificación de edad.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` y `expires_at` son obligatorios.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` y `message_id` deben ser enteros válidos.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "El archivo adjunto \"{filename}\" debe ser un archivo de imagen (png, jpg, jpeg, webp o gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "No se encontró el archivo adjunto con ID {attachmentId} en el mensaje.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "No se permiten archivos adjuntos para este mensaje.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "No se encontró contenido multimedia válido en el mensaje.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "La vista previa debe ser JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "El archivo adjunto referenciado \"{filename}\" no se encontró en los adjuntos del mensaje.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` debe ser un número entero válido.",
|
||||
"attachments_and_uploads.too_many_files": "Demasiados archivos. Se permite un máximo de {maxFiles, plural, one {# archivo} other {# archivos}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Se detectó una URL `attachment://` sin resolver. Debería haberse resuelto antes de procesar el contenido incrustado.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "No se encontró el archivo adjunto subido {filename}.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Has alcanzado el máximo de {count} {count, plural, one {emoji} other {emojis}}.",
|
||||
"limits.at_least_one_entry_required": "Se requiere al menos una entrada.",
|
||||
"limits.base64_length_invalid": "La longitud de la cadena Base64 debe estar entre {min} y {maxLength} caracteres.",
|
||||
"limits.bucket_required": "`bucket` es obligatorio.",
|
||||
"limits.cannot_shrink_reserved_slots": "No puedes disminuir los espacios reservados.",
|
||||
"limits.cannot_specify_before_and_after": "No puedes especificar `before` y `after` a la vez.",
|
||||
"limits.content_exceeds_max_length": "El texto no debe exceder los {maxLength} {maxLength, plural, one {carácter} other {caracteres}}.",
|
||||
"limits.integer_out_of_int64_range": "El valor entero está fuera del rango válido de int64.",
|
||||
"limits.rows_required": "`rows` es requerido.",
|
||||
"limits.string_length_exact": "La cadena debe tener exactamente {length, plural, one {# carácter} many {# caracteres} other {# caracteres}}.",
|
||||
"limits.string_length_invalid": "La longitud de la cadena debe tener entre {min} y {max} caracteres.",
|
||||
"limits.synced_preferences_too_large": "Las preferencias sincronizadas superan los {max_bytes} bytes.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "No se pudo actualizar el recurso. Inténtalo de nuevo.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "El usuario debe ser un bot para ser marcado como usuario del sistema.",
|
||||
"age_verification.already_verified": "Ya has completado la verificación de edad.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` y `expires_at` son obligatorios.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` y `message_id` deben ser enteros válidos.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "El archivo adjunto \"{filename}\" debe ser un archivo de imagen (png, jpg, jpeg, webp o gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "No se encontró el archivo adjunto con ID {attachmentId} en el mensaje.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "No se permiten archivos adjuntos para este mensaje.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "No se encontró contenido multimedia válido en el mensaje.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "La vista previa debe ser JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "El archivo adjunto referenciado \"{filename}\" no se encontró entre los archivos adjuntos del mensaje.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` debe ser un número entero válido.",
|
||||
"attachments_and_uploads.too_many_files": "Demasiados archivos. Se permite un máximo de {maxFiles} {maxFiles, plural, one {archivo} other {archivos}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Se detectó una URL `attachment://` sin resolver. Debería haberse resuelto antes de procesar el contenido incrustado.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "No se encontró el archivo adjunto subido con el nombre {filename}.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Has alcanzado el máximo de {count} {count, plural, one {emoji} other {emojis}}.",
|
||||
"limits.at_least_one_entry_required": "Es necesaria al menos una entrada.",
|
||||
"limits.base64_length_invalid": "La longitud de la cadena Base64 debe estar entre {min} y {maxLength} caracteres.",
|
||||
"limits.bucket_required": "`bucket` es necesario.",
|
||||
"limits.cannot_shrink_reserved_slots": "No puedes reducir los espacios reservados.",
|
||||
"limits.cannot_specify_before_and_after": "No puedes especificar `before` y `after` a la vez.",
|
||||
"limits.content_exceeds_max_length": "El texto no debe exceder {maxLength} {maxLength, plural, one {carácter} other {caracteres}}.",
|
||||
"limits.integer_out_of_int64_range": "El valor entero está fuera del rango válido de int64.",
|
||||
"limits.rows_required": "`rows` es necesario.",
|
||||
"limits.string_length_exact": "La cadena debe tener exactamente {length, plural, one {# carácter} many {# caracteres} other {# caracteres}}.",
|
||||
"limits.string_length_invalid": "La cadena debe tener una longitud entre {min} y {max} caracteres.",
|
||||
"limits.synced_preferences_too_large": "Las preferencias sincronizadas superan los {max_bytes} bytes.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Resurssia ei voitu päivittää. Yritä uudelleen.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Käyttäjän on oltava botti, jotta sen voi merkitä järjestelmäkäyttäjäksi.",
|
||||
"age_verification.already_verified": "Olet jo suorittanut iän vahvistuksen.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` ja `expires_at` ovat pakollisia.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "Seuraavien kenttien arvojen on oltava kelvollisia kokonaislukuja: `attachment_id`, `channel_id` ja `message_id`.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Liitteen \"{filename}\" on oltava kuvatiedosto (png, jpg, jpeg, webp tai gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Viestistä ei löytynyt liitettä {attachmentId}.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Tässä viestissä liitteet eivät ole sallittuja.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Viestistä ei löytynyt kelvollista mediatiedostoa.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Esikatselun on oltava JPEG-muodossa.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Viitattua liitettä \"{filename}\" ei löytynyt viestin liitteiden joukosta.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes`-arvon on oltava kelvollinen kokonaisluku.",
|
||||
"attachments_and_uploads.too_many_files": "Liian monta tiedostoa. Sallittu enimmäismäärä on {maxFiles, plural, one {# tiedosto} other {# tiedostoa}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Ratkaisematon `attachment://`-URL havaittu. Se olisi tullut ratkaista ennen upotusten käsittelyä.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Ladattua liitettä {filename} ei löytynyt.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Olet saavuttanut emojien enimmäismäärän: {count, plural, one {# emoji} other {# emojia}}.",
|
||||
"limits.at_least_one_entry_required": "Vähintään yksi merkintä vaaditaan.",
|
||||
"limits.base64_length_invalid": "Base64-merkkijonon pituuden on oltava {min}–{maxLength} merkkiä.",
|
||||
"limits.bucket_required": "`bucket` on pakollinen.",
|
||||
"limits.cannot_shrink_reserved_slots": "Et voi vähentää varattujen paikkojen määrää.",
|
||||
"limits.cannot_specify_before_and_after": "Et voi määrittää sekä `before`- että `after`-arvoja.",
|
||||
"limits.content_exceeds_max_length": "Tekstin pituus saa olla enintään {maxLength, plural, one {# merkki} other {# merkkiä}}.",
|
||||
"limits.integer_out_of_int64_range": "Kokonaislukuarvo on int64-alueen ulkopuolella.",
|
||||
"limits.rows_required": "`rows` on pakollinen.",
|
||||
"limits.string_length_exact": "Merkkijonon on oltava tasan {length, plural, one {# merkki} other {# merkkiä}} pitkä.",
|
||||
"limits.string_length_invalid": "Merkkijonon pituuden on oltava {min}–{max} merkkiä.",
|
||||
"limits.synced_preferences_too_large": "Synkronoidut asetukset ylittävät {max_bytes} tavua.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Impossible de mettre à jour la ressource. Réessayez.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "L'utilisateur doit être un bot pour être marqué comme utilisateur système.",
|
||||
"age_verification.already_verified": "Vous avez déjà effectué la vérification de l’âge.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` et `expires_at` sont nécessaires.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` et `message_id` doivent être des entiers valides.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "La pièce jointe \"{filename}\" doit être un fichier image (png, jpg, jpeg, webp ou gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "La pièce jointe avec l'ID {attachmentId} n'a pas été trouvée dans le message.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Les pièces jointes ne sont pas autorisées pour ce message.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Aucun média valide n'a été trouvé dans le message.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "L'aperçu doit être au format JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "La pièce jointe référencée \"{filename}\" n'a pas été trouvée dans les pièces jointes du message.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` doit être un entier valide.",
|
||||
"attachments_and_uploads.too_many_files": "Trop de fichiers. {maxFiles, plural, =1 {Un seul fichier est autorisé.} other {Le nombre maximal de fichiers autorisés est de #.}}",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Une URL `attachment://` non résolue a été détectée. Elle aurait dû être résolue avant le traitement du contenu intégré.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "La pièce jointe envoyée {filename} n'a pas été trouvée.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Vous avez atteint le maximum de {count, plural, =1 {# emoji} other {# emojis}}.",
|
||||
"limits.at_least_one_entry_required": "Au moins un élément est requis.",
|
||||
"limits.base64_length_invalid": "La longueur de la chaîne Base64 doit être comprise entre {min} et {maxLength} caractères.",
|
||||
"limits.bucket_required": "`bucket` est obligatoire.",
|
||||
"limits.cannot_shrink_reserved_slots": "Vous ne pouvez pas réduire le nombre d’emplacements réservés.",
|
||||
"limits.cannot_specify_before_and_after": "Vous ne pouvez pas indiquer à la fois `before` et `after`.",
|
||||
"limits.content_exceeds_max_length": "Le texte ne doit pas dépasser {maxLength, plural, =1 {# caractère} other {# caractères}}.",
|
||||
"limits.integer_out_of_int64_range": "La valeur entière est en dehors de l'intervalle int64 valide.",
|
||||
"limits.rows_required": "`rows` est obligatoire.",
|
||||
"limits.string_length_exact": "La chaîne doit contenir exactement {length, plural, =1 {# caractère} other {# caractères}}.",
|
||||
"limits.string_length_invalid": "La longueur de la chaîne doit être comprise entre {min} et {max} caractères.",
|
||||
"limits.synced_preferences_too_large": "Les préférences synchronisées dépassent {max_bytes} octets.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "לא הצלחנו לעדכן את המשאב. ניתן לנסות שוב.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "המשתמש חייב להיות בוט כדי להיות מוגדר כמשתמש מערכת.",
|
||||
"age_verification.already_verified": "כבר השלמת את אימות הגיל.",
|
||||
"attachments_and_uploads.attachment_fields_required": "יש לציין את `attachment_id`, `channel_id`, `message_id` ו-`expires_at`.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` ו-`message_id` חייבים להיות מספרים שלמים תקינים.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "הקובץ המצורף \"{filename}\" חייב להיות קובץ תמונה (png, jpg, jpeg, webp או gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "הקובץ המצורף עם המזהה {attachmentId} לא נמצא בהודעה.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "קבצים מצורפים אסורים בהודעה זו.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "לא נמצא בהודעה קובץ מדיה תקין.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "התצוגה המקדימה חייבת להיות בפורמט JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "הקובץ המצורף \"{filename}\" שאליו יש הפניה לא נמצא בין הקבצים המצורפים להודעה.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` חייב להיות מספר שלם תקין.",
|
||||
"attachments_and_uploads.too_many_files": "יותר מדי קבצים. מותרים לכל היותר {maxFiles} {maxFiles, plural, one {קובץ} other {קבצים}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "זוהתה כתובת URL של `attachment://` שלא נפתרה. היה עליה להיפתר לפני עיבוד ההטמעה.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "הקובץ המצורף שהועלה {filename} לא נמצא.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "הגעת למספר המרבי של {count} {count, plural, one {אימוג'י} other {אימוג'ים}}.",
|
||||
"limits.at_least_one_entry_required": "נדרשת רשומה אחת לפחות.",
|
||||
"limits.base64_length_invalid": "אורך מחרוזת Base64 חייב להיות בין {min} ל-{maxLength} תווים.",
|
||||
"limits.bucket_required": "`bucket` נדרש.",
|
||||
"limits.cannot_shrink_reserved_slots": "לא ניתן להקטין את המקומות השמורים.",
|
||||
"limits.cannot_specify_before_and_after": "לא ניתן לציין את `before` ואת `after` יחד.",
|
||||
"limits.content_exceeds_max_length": "הטקסט מוגבל ל-{maxLength} {maxLength, plural, one {תו} other {תווים}}.",
|
||||
"limits.integer_out_of_int64_range": "הערך השלם חורג מהטווח התקין של int64.",
|
||||
"limits.rows_required": "`rows` נדרש.",
|
||||
"limits.string_length_exact": "אורך המחרוזת חייב להיות בדיוק {length} {length, plural, one {תו} other {תווים}}.",
|
||||
"limits.string_length_invalid": "אורך המחרוזת חייב להיות בין {min} ל-{max} תווים.",
|
||||
"limits.synced_preferences_too_large": "ההעדפות המסונכרנות חורגות מ-{max_bytes} בתים.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "रिसोर्स अपडेट नहीं किया जा सका। कृपया फिर से कोशिश करें।",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "सिस्टम यूज़र के रूप में मार्क किए जाने के लिए यूज़र का बॉट होना ज़रूरी है।",
|
||||
"age_verification.already_verified": "आपने पहले ही एज वेरिफ़िकेशन पूरा कर लिया है।",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id`, और `expires_at` ज़रूरी हैं।",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id`, और `message_id` मान्य इंटीजर होने चाहिए।",
|
||||
"attachments_and_uploads.attachment_must_be_image": "अटैचमेंट \"{filename}\" एक इमेज फ़ाइल (png, jpg, jpeg, webp, या gif) होना चाहिए।",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "ID {attachmentId} वाला अटैचमेंट मैसेज में नहीं मिला।",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "इस मैसेज के लिए अटैचमेंट की अनुमति नहीं है।",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "मैसेज में कोई मान्य मीडिया नहीं मिला।",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "प्रीव्यू JPEG होना चाहिए।",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "जिस अटैचमेंट \"{filename}\" का ज़िक्र किया गया था, वह मैसेज अटैचमेंट में नहीं मिला।",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` मान्य इंटीजर होना चाहिए।",
|
||||
"attachments_and_uploads.too_many_files": "बहुत ज़्यादा फ़ाइलें। अधिकतम {maxFiles} {maxFiles, plural, one {फ़ाइल} other {फ़ाइल}} की अनुमति है।",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "अनसुलझा `attachment://` URL पाया गया। इसे एम्बेड प्रोसेसिंग से पहले हल किया जाना चाहिए था।",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "अपलोड किया गया अटैचमेंट {filename} नहीं मिला।",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "आप अधिकतम {count} {count, plural, one {इमोजी} other {इमोजी}} तक पहुँच गए हैं।",
|
||||
"limits.at_least_one_entry_required": "कम से कम एक एंट्री ज़रूरी है।",
|
||||
"limits.base64_length_invalid": "Base64 स्ट्रिंग की लंबाई {min} और {maxLength} कैरेक्टर के बीच होनी चाहिए।",
|
||||
"limits.bucket_required": "`bucket` ज़रूरी है।",
|
||||
"limits.cannot_shrink_reserved_slots": "आप रिज़र्व किए गए स्लॉट कम नहीं कर सकते।",
|
||||
"limits.cannot_specify_before_and_after": "आप `before` और `after` दोनों को स्पेसिफाई नहीं कर सकते।",
|
||||
"limits.content_exceeds_max_length": "टेक्स्ट {maxLength} {maxLength, plural, one {कैरेक्टर} other {कैरेक्टर}} से ज़्यादा नहीं होना चाहिए।",
|
||||
"limits.integer_out_of_int64_range": "इंटीजर वैल्यू मान्य int64 रेंज से बाहर है।",
|
||||
"limits.rows_required": "`rows` ज़रूरी है।",
|
||||
"limits.string_length_exact": "स्ट्रिंग की लंबाई ठीक {length, plural, one {# कैरेक्टर} other {# कैरेक्टर}} होनी चाहिए।",
|
||||
"limits.string_length_invalid": "स्ट्रिंग की लंबाई {min} और {max} कैरेक्टर के बीच होनी चाहिए।",
|
||||
"limits.synced_preferences_too_large": "सिंक की गई सेटिंग्स {max_bytes} बाइट्स से ज़्यादा हैं।",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Nismo mogli ažurirati resurs. Pokušaj ponovno.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Korisnik mora biti bot da bi bio označen kao sistemski korisnik.",
|
||||
"age_verification.already_verified": "Provjera dobi je već završena.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` i `expires_at` su obavezni.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` i `message_id` moraju biti valjani cijeli brojevi.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Privitak \"{filename}\" mora biti slika (png, jpg, jpeg, webp ili gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Privitak s ID-om {attachmentId} nije pronađen u poruci.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Privici nisu dopušteni za ovu poruku.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Nije pronađen valjan medij u poruci.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Pretpregled mora biti JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Referencirani privitak \"{filename}\" nije pronađen u privicima poruke.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` mora biti valjan cijeli broj.",
|
||||
"attachments_and_uploads.too_many_files": "Previše datoteka. Dopušteno je najviše {maxFiles, plural, one {# datoteka} few {# datoteke} other {# datoteka}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Otkriven je nerazriješeni `attachment://` URL. Trebao je biti razriješen prije obrade ugrađenog sadržaja.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Preneseni privitak {filename} nije pronađen.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Dosegnut je maksimum od {count, plural, one {# emojija} few {# emojija} other {# emojija}}.",
|
||||
"limits.at_least_one_entry_required": "Potreban je barem jedan unos.",
|
||||
"limits.base64_length_invalid": "Duljina Base64 niza mora biti između {min} i {maxLength} znakova.",
|
||||
"limits.bucket_required": "`bucket` je obavezan.",
|
||||
"limits.cannot_shrink_reserved_slots": "Ne možeš smanjiti rezervirane utore.",
|
||||
"limits.cannot_specify_before_and_after": "Ne možeš navesti i `before` i `after`.",
|
||||
"limits.content_exceeds_max_length": "Tekst ne smije prelaziti {maxLength, plural, one {# znak} few {# znaka} other {# znakova}}.",
|
||||
"limits.integer_out_of_int64_range": "Cjelobrojna vrijednost je izvan važećeg raspona za int64.",
|
||||
"limits.rows_required": "`rows` je obavezan.",
|
||||
"limits.string_length_exact": "Niz znakova mora imati točno {length, plural, one {# znak} few {# znaka} other {# znakova}}.",
|
||||
"limits.string_length_invalid": "Duljina niza mora biti između {min} i {max} znakova.",
|
||||
"limits.synced_preferences_too_large": "Sinkronizirane postavke premašuju {max_bytes} bajtova.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Nem sikerült frissíteni az erőforrást. Próbáld újra.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Csak botfelhasználó jelölhető meg rendszerfelhasználóként.",
|
||||
"age_verification.already_verified": "Már elvégezted a korhatár-ellenőrzést.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` és `expires_at` megadása kötelező.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` és `message_id` érvényes egész számoknak kell lenniük.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "A(z) \"{filename}\" mellékletnek képfájlnak kell lennie (png, jpg, jpeg, webp vagy gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "A(z) {attachmentId} azonosítójú mellékletet nem találtuk az üzenetben.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Ehhez az üzenethez nem engedélyezettek a mellékletek.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Nem található érvényes média az üzenetben.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Az előnézet csak JPEG formátumú lehet.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "A hivatkozott \"{filename}\" melléklet nem szerepel az üzenet mellékletei között.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "A `size_bytes` értékének érvényes egész számnak kell lennie.",
|
||||
"attachments_and_uploads.too_many_files": "Túl sok fájl. Legfeljebb {maxFiles, plural, one {# fájl} other {# fájl}} engedélyezett.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Feloldatlan `attachment://` URL található. Ezt a beágyazott tartalom feldolgozása előtt fel kellett volna oldani.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "A feltöltött \"{filename}\" melléklet nem található.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Elérted az emojik megengedett számát ({count, plural, one {#} other {#}}).",
|
||||
"limits.at_least_one_entry_required": "Legalább egy bejegyzés szükséges.",
|
||||
"limits.base64_length_invalid": "A Base64 karakterlánc hossza {min} és {maxLength} karakter közötti lehet.",
|
||||
"limits.bucket_required": "`bucket` szükséges.",
|
||||
"limits.cannot_shrink_reserved_slots": "A lefoglalt helyek számát nem csökkentheted.",
|
||||
"limits.cannot_specify_before_and_after": "Nem adhatod meg egyszerre a `before` és az `after` paramétert.",
|
||||
"limits.content_exceeds_max_length": "A szöveg legfeljebb {maxLength, plural, one {# karakter} other {# karakter}} hosszú lehet.",
|
||||
"limits.integer_out_of_int64_range": "Az egész érték kívül esik az érvényes int64-tartományon.",
|
||||
"limits.rows_required": "`rows` szükséges.",
|
||||
"limits.string_length_exact": "A karakterlánc hossza pontosan {length, plural, one {# karakter} other {# karakter}} legyen.",
|
||||
"limits.string_length_invalid": "A karakterlánc hossza {min} és {max} karakter közötti lehet.",
|
||||
"limits.synced_preferences_too_large": "A szinkronizált beállítások mérete meghaladja a {max_bytes} bájtot.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Tidak bisa memperbarui sumber daya. Coba lagi.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Pengguna harus bot untuk ditandai sebagai pengguna sistem.",
|
||||
"age_verification.already_verified": "Kamu sudah menyelesaikan verifikasi usia.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id`, dan `expires_at` diperlukan.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id`, dan `message_id` harus berupa bilangan bulat yang valid.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Lampiran \"{filename}\" harus berupa gambar (png, jpg, jpeg, webp, atau gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Lampiran dengan ID {attachmentId} tidak ditemukan di pesan.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Lampiran tidak diizinkan untuk pesan ini.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Tidak ada media yang valid di pesan.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Pratinjau harus berupa JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Lampiran \"{filename}\" yang dirujuk tidak ditemukan di lampiran pesan.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` harus berupa bilangan bulat yang valid.",
|
||||
"attachments_and_uploads.too_many_files": "Terlalu banyak file. Maksimal {maxFiles} {maxFiles, plural, other {file}} diizinkan.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Terdeteksi URL `attachment://` yang belum diselesaikan. Ini seharusnya sudah diselesaikan sebelum pemrosesan sematan.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Lampiran {filename} yang diunggah tidak ditemukan.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Kamu sudah mencapai batas maksimum {count} {count, plural, other {emoji}}.",
|
||||
"limits.at_least_one_entry_required": "Minimal satu entri diperlukan.",
|
||||
"limits.base64_length_invalid": "Panjang string Base64 harus antara {min} dan {maxLength} karakter.",
|
||||
"limits.bucket_required": "`bucket` diperlukan.",
|
||||
"limits.cannot_shrink_reserved_slots": "Kamu tidak bisa mengurangi slot yang dicadangkan.",
|
||||
"limits.cannot_specify_before_and_after": "Kamu tidak bisa menentukan `before` dan `after` sekaligus.",
|
||||
"limits.content_exceeds_max_length": "Teks tidak boleh melebihi {maxLength} {maxLength, plural, other {karakter}}.",
|
||||
"limits.integer_out_of_int64_range": "Nilai integer di luar rentang int64 yang valid.",
|
||||
"limits.rows_required": "`rows` diperlukan.",
|
||||
"limits.string_length_exact": "String harus berisi tepat {length, plural, other {# karakter}}.",
|
||||
"limits.string_length_invalid": "Panjang string harus antara {min} dan {max} karakter.",
|
||||
"limits.synced_preferences_too_large": "Preferensi yang disinkronkan melebihi {max_bytes} byte.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Non è stato possibile aggiornare la risorsa. Riprova.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "L'utente deve essere un bot per essere contrassegnato come utente di sistema.",
|
||||
"age_verification.already_verified": "Hai già completato la verifica dell'età.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` e `expires_at` sono richiesti.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` e `message_id` devono essere interi validi.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "L'allegato \"{filename}\" deve essere un file immagine (png, jpg, jpeg, webp o gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "L'allegato con ID {attachmentId} non è stato trovato nel messaggio.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Gli allegati non sono consentiti per questo messaggio.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Nessun media valido è stato trovato nel messaggio.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "L'anteprima deve essere JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "L'allegato di riferimento \"{filename}\" non è stato trovato negli allegati del messaggio.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` deve essere un numero intero valido.",
|
||||
"attachments_and_uploads.too_many_files": "Troppi file. Il limite è di {maxFiles, plural, one {# file} other {# file}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Rilevato URL `attachment://` non risolto. Avrebbe dovuto essere risolto prima dell’elaborazione del contenuto incorporato.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "L'allegato caricato {filename} non è stato trovato.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Hai raggiunto il massimo di {count, plural, one {# emoji} other {# emoji}}.",
|
||||
"limits.at_least_one_entry_required": "È richiesta almeno una voce.",
|
||||
"limits.base64_length_invalid": "La lunghezza della stringa base64 deve essere compresa tra {min} e {maxLength} caratteri.",
|
||||
"limits.bucket_required": "`bucket` è richiesto.",
|
||||
"limits.cannot_shrink_reserved_slots": "Non puoi diminuire gli slot riservati.",
|
||||
"limits.cannot_specify_before_and_after": "Non puoi specificare sia `before` che `after`.",
|
||||
"limits.content_exceeds_max_length": "Il testo non può superare {maxLength, plural, one {# carattere} other {# caratteri}}.",
|
||||
"limits.integer_out_of_int64_range": "Il valore intero è fuori dall'intervallo valido di int64.",
|
||||
"limits.rows_required": "`rows` è richiesto.",
|
||||
"limits.string_length_exact": "La stringa deve contenere esattamente {length, plural, one {# carattere} many {# caratteri} other {# caratteri}}.",
|
||||
"limits.string_length_invalid": "La lunghezza della stringa deve essere compresa tra {min} e {max} caratteri.",
|
||||
"limits.synced_preferences_too_large": "Le preferenze sincronizzate superano i {max_bytes} byte.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "リソースを更新できませんでした。もう一度お試しください。",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "システムユーザーとしてマークするには、ユーザーはボットである必要があります。",
|
||||
"age_verification.already_verified": "年齢確認はすでに完了しています。",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`、`channel_id`、`message_id`、`expires_at`は必須です。",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`、`channel_id`、`message_id`は有効な整数である必要があります。",
|
||||
"attachments_and_uploads.attachment_must_be_image": "添付ファイル「{filename}」は画像ファイル(png、jpg、jpeg、webp、またはgif)である必要があります。",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "添付ファイル(ID: {attachmentId})がメッセージ内に見つかりませんでした。",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "このメッセージには添付ファイルを使用できません。",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "メッセージ内に有効なメディアが見つかりませんでした。",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "プレビューはJPEG形式にしてください。",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "参照された添付ファイル「{filename}」がメッセージの添付ファイルに見つかりませんでした。",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes`は有効な整数である必要があります。",
|
||||
"attachments_and_uploads.too_many_files": "ファイルが多すぎます。最大{maxFiles, plural, other {#個}}のファイルまでです。",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "未解決の`attachment://` URLが検出されました。これは埋め込み処理の前に解決されているはずです。",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "アップロードされた添付ファイル「{filename}」が見つかりませんでした。",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "絵文字の上限{count, plural, other {#個}}に達しました。",
|
||||
"limits.at_least_one_entry_required": "1つ以上のエントリが必要です。",
|
||||
"limits.base64_length_invalid": "Base64文字列の長さは{min}~{maxLength}文字である必要があります。",
|
||||
"limits.bucket_required": "`bucket`は必須です。",
|
||||
"limits.cannot_shrink_reserved_slots": "予約済みスロットを減らすことはできません。",
|
||||
"limits.cannot_specify_before_and_after": "`before`と`after`は同時に指定できません。",
|
||||
"limits.content_exceeds_max_length": "テキストは{maxLength, plural, other {#文字}}を超えることはできません。",
|
||||
"limits.integer_out_of_int64_range": "整数値がint64の有効範囲外です。",
|
||||
"limits.rows_required": "`rows`は必須です。",
|
||||
"limits.string_length_exact": "文字列はちょうど{length, plural, other {#文字}}である必要があります。",
|
||||
"limits.string_length_invalid": "文字列の長さは{min}~{max}文字である必要があります。",
|
||||
"limits.synced_preferences_too_large": "同期された設定が{max_bytes}バイトを超えています。",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "리소스를 업데이트하지 못했어요. 다시 시도해 주세요.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "시스템 사용자로 표시하려면 봇이어야 해요.",
|
||||
"age_verification.already_verified": "연령 인증을 이미 완료했어요.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id`, `expires_at`이 모두 필요해요.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id`, `message_id`는 모두 유효한 정수여야 해요.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "첨부 파일 \"{filename}\"은 이미지 파일(png, jpg, jpeg, webp 또는 gif)이어야 해요.",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "ID가 {attachmentId}인 첨부 파일을 메시지에서 찾을 수 없어요.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "이 메시지에는 첨부 파일을 추가할 수 없어요.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "메시지에서 유효한 미디어를 찾을 수 없어요.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "미리보기는 JPEG 형식이어야 해요.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "참조된 첨부 파일 \"{filename}\"을 메시지 첨부 파일에서 찾을 수 없어요.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes`는 유효한 정수여야 해요.",
|
||||
"attachments_and_uploads.too_many_files": "파일이 너무 많아요. 최대 {maxFiles, plural, other {#개}}까지 허용돼요.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "미해결 `attachment://` URL이 감지되었어요. 임베드 처리 전에 해결되었어야 해요.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "업로드한 첨부 파일 {filename}을 찾을 수 없어요.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "최대 {count, plural, other {#개}}의 이모지에 도달했어요.",
|
||||
"limits.at_least_one_entry_required": "최소 한 개의 항목이 필요해요.",
|
||||
"limits.base64_length_invalid": "Base64 문자열 길이는 {min}자에서 {maxLength}자 사이여야 해요.",
|
||||
"limits.bucket_required": "`bucket`은 필수 항목이에요.",
|
||||
"limits.cannot_shrink_reserved_slots": "예약된 슬롯을 줄일 수 없어요.",
|
||||
"limits.cannot_specify_before_and_after": "`before`와 `after`를 동시에 지정할 수 없어요.",
|
||||
"limits.content_exceeds_max_length": "텍스트는 {maxLength, plural, other {#자}}를 초과할 수 없어요.",
|
||||
"limits.integer_out_of_int64_range": "정수 값이 유효한 int64 범위를 벗어났어요.",
|
||||
"limits.rows_required": "`rows`는 필수 항목이에요.",
|
||||
"limits.string_length_exact": "문자열은 정확히 {length, plural, other {#자}}여야 해요.",
|
||||
"limits.string_length_invalid": "문자열 길이는 {min}자에서 {max}자 사이여야 해요.",
|
||||
"limits.synced_preferences_too_large": "동기화된 기본 설정이 {max_bytes}바이트를 초과했어요.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Ištekliaus atnaujinti nepavyko. Pabandyk dar kartą.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Naudotojas turi būti botas, kad jį būtų galima pažymėti kaip sistemos naudotoją.",
|
||||
"age_verification.already_verified": "Amžiaus patvirtinimą jau atlikai.",
|
||||
"attachments_and_uploads.attachment_fields_required": "Reikalingi: `attachment_id`, `channel_id`, `message_id` ir `expires_at`.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` ir `message_id` turi būti galiojantys sveikieji skaičiai.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Priedas \"{filename}\" turi būti paveikslėlis (png, jpg, jpeg, webp arba gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Žinutėje nerastas priedas su ID {attachmentId}.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Priedai šiai žinutei neleidžiami.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Žinutėje nerasta galiojančios medijos.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Peržiūra turi būti JPEG formato.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Nurodytas priedas \"{filename}\" nebuvo rastas žinutės prieduose.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` turi būti galiojantis sveikasis skaičius.",
|
||||
"attachments_and_uploads.too_many_files": "Per daug failų. Leidžiama daugiausia {maxFiles, plural, one {# failas} few {# failai} many {# failo} other {# failų}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Aptiktas neišspręstas `attachment://` URL. Jis turėjo būti išspręstas prieš apdorojant įterptuosius elementus.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Įkeltas priedas {filename} nerastas.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Pasiekei {count, plural, one {# emoji} few {# emoji} many {# emoji} other {# emoji}} ribą.",
|
||||
"limits.at_least_one_entry_required": "Reikalingas bent vienas įrašas.",
|
||||
"limits.base64_length_invalid": "Base64 eilutės ilgis turi būti nuo {min} iki {maxLength} simbolių.",
|
||||
"limits.bucket_required": "`bucket` reikalingas.",
|
||||
"limits.cannot_shrink_reserved_slots": "Negali sumažinti rezervuotų vietų.",
|
||||
"limits.cannot_specify_before_and_after": "Negali nurodyti `before` ir `after` vienu metu.",
|
||||
"limits.content_exceeds_max_length": "Tekstas neturi viršyti {maxLength, plural, one {# simbolio} few {# simbolių} many {# simbolio} other {# simbolių}}.",
|
||||
"limits.integer_out_of_int64_range": "Sveikojo skaičiaus reikšmė yra už galiojančio int64 diapazono ribų.",
|
||||
"limits.rows_required": "`rows` reikalingas.",
|
||||
"limits.string_length_exact": "Eilutės ilgis turi būti lygiai {length, plural, one {# simbolis} few {# simboliai} many {# simbolio} other {# simbolių}}.",
|
||||
"limits.string_length_invalid": "Eilutės ilgis turi būti nuo {min} iki {max} simbolių.",
|
||||
"limits.synced_preferences_too_large": "Sinchronizuotų nuostatų dydis viršija {max_bytes} baitus.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "We konden het item niet bijwerken. Probeer het opnieuw.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Gebruiker moet een bot zijn om als systeemgebruiker te worden gemarkeerd.",
|
||||
"age_verification.already_verified": "Je hebt de leeftijdsverificatie al voltooid.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` en `expires_at` zijn verplicht.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` en `message_id` moeten geldige gehele getallen zijn.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Bijlage \"{filename}\" moet een afbeeldingsbestand zijn (png, jpg, jpeg, webp of gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Bijlage met ID {attachmentId} is niet gevonden in het bericht.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Bijlagen zijn niet toegestaan voor dit bericht.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Geen geldige media gevonden in het bericht.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Het voorbeeld moet het JPEG-formaat hebben.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "De bijlage \"{filename}\" waarnaar wordt verwezen, is niet gevonden in de bijlagen van het bericht.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` moet een geldig geheel getal zijn.",
|
||||
"attachments_and_uploads.too_many_files": "Te veel bestanden. Maximaal {maxFiles, plural, one {# bestand} other {# bestanden}} toegestaan.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Onopgeloste `attachment://`-URL gedetecteerd. Dit had moeten worden opgelost vóór het verwerken van de ingesloten inhoud.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "De geüploade bijlage {filename} is niet gevonden.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Je hebt het maximum van {count, plural, one {# emoji} other {# emoji’s}} bereikt.",
|
||||
"limits.at_least_one_entry_required": "Minimaal één invoer is vereist.",
|
||||
"limits.base64_length_invalid": "De Base64-string moet tussen {min} en {maxLength} tekens lang zijn.",
|
||||
"limits.bucket_required": "`bucket` is vereist.",
|
||||
"limits.cannot_shrink_reserved_slots": "Je kunt het aantal gereserveerde slots niet verminderen.",
|
||||
"limits.cannot_specify_before_and_after": "Je kunt niet zowel `before` als `after` opgeven.",
|
||||
"limits.content_exceeds_max_length": "Tekst mag niet meer dan {maxLength, plural, one {# teken} other {# tekens}} bevatten.",
|
||||
"limits.integer_out_of_int64_range": "De waarde valt buiten het geldige int64-bereik.",
|
||||
"limits.rows_required": "`rows` is vereist.",
|
||||
"limits.string_length_exact": "De string moet precies {length, plural, one {# teken} other {# tekens}} bevatten.",
|
||||
"limits.string_length_invalid": "De string moet tussen {min} en {max} tekens lang zijn.",
|
||||
"limits.synced_preferences_too_large": "Gesynchroniseerde voorkeuren overschrijden {max_bytes} bytes.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Vi kunne ikke oppdatere ressursen. Prøv på nytt.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Brukeren må være en bot for å bli merket som en systembruker.",
|
||||
"age_verification.already_verified": "Du har allerede fullført aldersverifisering.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` og `expires_at` er påkrevd.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` og `message_id` må være gyldige heltall.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Vedlegget «{filename}» må være en bildefil (png, jpg, jpeg, webp eller gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Vedlegget med ID {attachmentId} ble ikke funnet i meldingen.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Vedlegg er ikke tillatt for denne meldingen.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Ingen gyldige medier ble funnet i meldingen.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Forhåndsvisningen må være i JPEG-format.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Vedlegget «{filename}» det refereres til, ble ikke funnet blant meldingsvedleggene.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` må være et gyldig heltall.",
|
||||
"attachments_and_uploads.too_many_files": "For mange filer. Maksimalt {maxFiles, plural, one {# fil} other {# filer}} er tillatt.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "En uløst `attachment://`-URL ble oppdaget. Denne skulle ha vært løst før behandling av innebygd innhold.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Det opplastede vedlegget {filename} ble ikke funnet.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Du har nådd grensen på {count, plural, one {# emoji} other {# emojier}}.",
|
||||
"limits.at_least_one_entry_required": "Minst én oppføring er påkrevd.",
|
||||
"limits.base64_length_invalid": "Base64-strengen må inneholde mellom {min} og {maxLength} tegn.",
|
||||
"limits.bucket_required": "`bucket` er påkrevd.",
|
||||
"limits.cannot_shrink_reserved_slots": "Du kan ikke redusere antallet reserverte plasser.",
|
||||
"limits.cannot_specify_before_and_after": "Du kan ikke angi både `before` og `after`.",
|
||||
"limits.content_exceeds_max_length": "Teksten kan ikke overstige {maxLength, plural, one {# tegn} other {# tegn}}.",
|
||||
"limits.integer_out_of_int64_range": "Heltallsverdien er utenfor det gyldige int64-området.",
|
||||
"limits.rows_required": "`rows` er påkrevd.",
|
||||
"limits.string_length_exact": "Strengen må være nøyaktig {length, plural, one {# tegn} other {# tegn}} lang.",
|
||||
"limits.string_length_invalid": "Strengen må inneholde mellom {min} og {max} tegn.",
|
||||
"limits.synced_preferences_too_large": "Synkroniserte innstillinger overskrider {max_bytes} byte.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Nie udało się zaktualizować zasobu. Spróbuj jeszcze raz.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Użytkownik musi być botem, aby mógł być oznaczony jako użytkownik systemowy.",
|
||||
"age_verification.already_verified": "Weryfikacja wieku została już zakończona.",
|
||||
"attachments_and_uploads.attachment_fields_required": "Wymagane: `attachment_id`, `channel_id`, `message_id` i `expires_at`.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` i `message_id` muszą być prawidłowymi liczbami całkowitymi.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Załącznik \"{filename}\" musi być plikiem graficznym (png, jpg, jpeg, webp lub gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Załącznika o ID {attachmentId} nie znaleziono w wiadomości.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Nie możesz dołączać załączników do tej wiadomości.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Nie znaleziono prawidłowych multimediów w wiadomości.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Podgląd musi być w formacie JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Nie znaleziono załącznika \"{filename}\" w załącznikach wiadomości.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` musi być poprawną liczbą całkowitą.",
|
||||
"attachments_and_uploads.too_many_files": "Zbyt wiele plików. {maxFiles, plural, one {Dozwolony jest maksymalnie # plik.} few {Dozwolone są maksymalnie # pliki.} many {Dozwolonych jest maksymalnie # plików.} other {Dozwolone jest maksymalnie # pliku.}}",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Wykryto nierozwiązany adres URL `attachment://`. Powinien zostać rozwiązany przed przetwarzaniem elementów osadzonych.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Nie znaleziono przesłanego załącznika {filename}.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Osiągnięto limit {count, plural, one {# emoji} few {# emoji} many {# emoji} other {# emoji}}.",
|
||||
"limits.at_least_one_entry_required": "Wymagany jest co najmniej jeden wpis.",
|
||||
"limits.base64_length_invalid": "Długość ciągu Base64 musi wynosić od {min} do {maxLength} znaków.",
|
||||
"limits.bucket_required": "`bucket` jest wymagany.",
|
||||
"limits.cannot_shrink_reserved_slots": "Nie możesz zmniejszyć liczby zarezerwowanych miejsc.",
|
||||
"limits.cannot_specify_before_and_after": "Nie możesz określić jednocześnie `before` i `after`.",
|
||||
"limits.content_exceeds_max_length": "Tekst nie może mieć więcej niż {maxLength, plural, one {# znak} few {# znaki} many {# znaków} other {# znaku}}.",
|
||||
"limits.integer_out_of_int64_range": "Wartość całkowita wykracza poza prawidłowy zakres int64.",
|
||||
"limits.rows_required": "`rows` jest wymagane.",
|
||||
"limits.string_length_exact": "Ciąg musi mieć dokładnie {length, plural, one {# znak} few {# znaki} many {# znaków} other {# znaku}}.",
|
||||
"limits.string_length_invalid": "Długość ciągu musi mieścić się w zakresie od {min} do {max} znaków.",
|
||||
"limits.synced_preferences_too_large": "Zsynchronizowane ustawienia przekraczają {max_bytes} bajtów.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Não foi possível atualizar o recurso. Tente de novo.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "O usuário deve ser um bot para ser marcado como usuário de sistema.",
|
||||
"age_verification.already_verified": "Você já concluiu a verificação de idade.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` e `expires_at` são obrigatórios.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` e `message_id` devem ser números inteiros válidos.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "O anexo \"{filename}\" deve ser um arquivo de imagem (png, jpg, jpeg, webp ou gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "O anexo com ID {attachmentId} não foi encontrado na mensagem.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Anexos não são permitidos para esta mensagem.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Nenhuma mídia válida foi encontrada na mensagem.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "A prévia deve estar no formato JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "O anexo referenciado \"{filename}\" não foi encontrado nos anexos da mensagem.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` deve ser um número inteiro válido.",
|
||||
"attachments_and_uploads.too_many_files": "Há arquivos demais. O limite é de {maxFiles, plural, =1 {# arquivo} other {# arquivos}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "URL `attachment://` não resolvida detectada. Isso deveria ter sido resolvido antes do processamento de incorporação.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "O anexo enviado {filename} não foi encontrado.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Você atingiu o máximo de {count, plural, =1 {# emoji} other {# emojis}}.",
|
||||
"limits.at_least_one_entry_required": "Pelo menos uma entrada é obrigatória.",
|
||||
"limits.base64_length_invalid": "O comprimento da string Base64 precisa estar entre {min} e {maxLength} caracteres.",
|
||||
"limits.bucket_required": "`bucket` é necessário.",
|
||||
"limits.cannot_shrink_reserved_slots": "Você não pode reduzir os slots reservados.",
|
||||
"limits.cannot_specify_before_and_after": "Você não pode especificar `before` e `after` ao mesmo tempo.",
|
||||
"limits.content_exceeds_max_length": "O texto não deve exceder {maxLength, plural, =1 {# caractere} other {# caracteres}}.",
|
||||
"limits.integer_out_of_int64_range": "O valor inteiro está fora do intervalo válido de int64.",
|
||||
"limits.rows_required": "`rows` é necessário.",
|
||||
"limits.string_length_exact": "A string precisa ter exatamente {length, plural, =0 {# caracteres} one {# caractere} many {# caracteres} other {# caracteres}}.",
|
||||
"limits.string_length_invalid": "O comprimento da string precisa estar entre {min} e {max} caracteres.",
|
||||
"limits.synced_preferences_too_large": "Preferências sincronizadas excedem {max_bytes} bytes.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Resursa nu a putut fi actualizată. Încearcă din nou.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Utilizatorul trebuie să fie un bot pentru a fi marcat ca utilizator de sistem.",
|
||||
"age_verification.already_verified": "Ai finalizat deja verificarea vârstei.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` și `expires_at` sunt obligatorii.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` și `message_id` trebuie să fie numere întregi valide.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Atașamentul \"{filename}\" trebuie să fie un fișier de imagine (png, jpg, jpeg, webp sau gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Atașamentul cu ID-ul {attachmentId} nu a fost găsit în mesaj.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Atașamentele nu sunt permise pentru acest mesaj.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Nu a fost găsit niciun conținut media valid în mesaj.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Previzualizarea trebuie să fie JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Atașamentul referențiat \"{filename}\" nu a fost găsit în atașamentele mesajului.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` trebuie să fie un număr întreg valid.",
|
||||
"attachments_and_uploads.too_many_files": "Prea multe fișiere. Sunt permise maximum {maxFiles, plural, one {# fișier} few {# fișiere} other {# de fișiere}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "S-a detectat o adresă URL `attachment://` nerezolvată. Aceasta ar fi trebuit rezolvată înainte de procesarea încorporărilor.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Atașamentul încărcat {filename} nu a fost găsit.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Ai atins numărul maxim de {count, plural, one {# emoji} few {# emoji} other {# de emoji}}.",
|
||||
"limits.at_least_one_entry_required": "Este necesară cel puțin o intrare.",
|
||||
"limits.base64_length_invalid": "Lungimea șirului Base64 trebuie să fie între {min} și {maxLength} caractere.",
|
||||
"limits.bucket_required": "`bucket` este obligatoriu.",
|
||||
"limits.cannot_shrink_reserved_slots": "Nu poți reduce sloturile rezervate.",
|
||||
"limits.cannot_specify_before_and_after": "Nu poți specifica atât `before`, cât și `after`.",
|
||||
"limits.content_exceeds_max_length": "Textul nu trebuie să depășească {maxLength, plural, one {# caracter} few {# caractere} other {# de caractere}}.",
|
||||
"limits.integer_out_of_int64_range": "Valoarea întreagă este în afara intervalului valid pentru int64.",
|
||||
"limits.rows_required": "`rows` este obligatoriu.",
|
||||
"limits.string_length_exact": "Șirul trebuie să conțină exact {length, plural, one {# caracter} few {# caractere} other {# de caractere}}.",
|
||||
"limits.string_length_invalid": "Lungimea șirului trebuie să fie între {min} și {max} caractere.",
|
||||
"limits.synced_preferences_too_large": "Preferințele sincronizate depășesc {max_bytes} octeți.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Не удалось обновить ресурс. Попробуй ещё раз.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Пользователь должен быть ботом, чтобы отметить его как системного пользователя.",
|
||||
"age_verification.already_verified": "Возраст уже подтверждён.",
|
||||
"attachments_and_uploads.attachment_fields_required": "Необходимо указать `attachment_id`, `channel_id`, `message_id` и `expires_at`.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` и `message_id` должны быть действительными целыми числами.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Вложение «{filename}» должно быть файлом изображения (png, jpg, jpeg, webp или gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Вложение с ID {attachmentId} не найдено в сообщении.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Вложения запрещены для этого сообщения.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "В сообщении не найдено подходящих медиафайлов.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Превью должно быть в формате JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Указанное вложение «{filename}» не найдено среди вложений сообщения.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` должно быть действительным целым числом.",
|
||||
"attachments_and_uploads.too_many_files": "Слишком много файлов. Разрешено максимум {maxFiles} {maxFiles, plural, one {файл} few {файла} many {файлов} other {файла}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Обнаружен неразрешённый URL `attachment://`. Он должен был быть разрешён до обработки встраивания.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Не удалось найти загруженное вложение {filename}.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Ты достиг максимума: {count} {count, plural, one {эмодзи} few {эмодзи} many {эмодзи} other {эмодзи}}.",
|
||||
"limits.at_least_one_entry_required": "Требуется хотя бы одна запись.",
|
||||
"limits.base64_length_invalid": "Длина строки Base64 должна быть от {min} до {maxLength} символов.",
|
||||
"limits.bucket_required": "Требуется `bucket`.",
|
||||
"limits.cannot_shrink_reserved_slots": "Нельзя уменьшить количество зарезервированных слотов.",
|
||||
"limits.cannot_specify_before_and_after": "Нельзя указывать `before` и `after` одновременно.",
|
||||
"limits.content_exceeds_max_length": "Текст не должен превышать {maxLength} {maxLength, plural, one {символ} few {символа} many {символов} other {символа}}.",
|
||||
"limits.integer_out_of_int64_range": "Целочисленное значение выходит за пределы допустимого диапазона int64.",
|
||||
"limits.rows_required": "Требуется `rows`.",
|
||||
"limits.string_length_exact": "Строка должна содержать ровно {length, plural, one {# символ} few {# символа} many {# символов} other {# символа}}.",
|
||||
"limits.string_length_invalid": "Длина строки должна быть от {min} до {max} символов.",
|
||||
"limits.synced_preferences_too_large": "Синхронизируемые настройки превышают {max_bytes} байт.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Det gick inte att uppdatera resursen. Försök igen.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Användaren måste vara en bot för att markeras som en systemanvändare.",
|
||||
"age_verification.already_verified": "Du har redan slutfört åldersverifieringen.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` och `expires_at` krävs.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` och `message_id` måste vara giltiga heltal.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Bilagan \"{filename}\" måste vara en bildfil (png, jpg, jpeg, webp eller gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Bilagan med ID {attachmentId} hittades inte i meddelandet.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Bilagor tillåts inte för detta meddelande.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Inget giltigt medieinnehåll hittades i meddelandet.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Förhandsgranskningsbilden måste vara i JPEG-format.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Den refererade bilagan \"{filename}\" hittades inte i meddelandets bilagor.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` måste vara ett giltigt heltal.",
|
||||
"attachments_and_uploads.too_many_files": "För många filer. Högst {maxFiles, plural, one {# fil} other {# filer}} tillåts.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "En olöst `attachment://`-URL upptäcktes. Detta skulle ha lösts innan inbäddningarna behandlades.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Den uppladdade bilagan {filename} hittades inte.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Du har nått gränsen på {count, plural, one {# emoji} other {# emojis}}.",
|
||||
"limits.at_least_one_entry_required": "Minst en post krävs.",
|
||||
"limits.base64_length_invalid": "Base64-strängens längd måste vara mellan {min} och {maxLength} tecken.",
|
||||
"limits.bucket_required": "`bucket` är obligatoriskt.",
|
||||
"limits.cannot_shrink_reserved_slots": "Du kan inte minska antalet reserverade platser.",
|
||||
"limits.cannot_specify_before_and_after": "Du kan inte ange både `before` och `after`.",
|
||||
"limits.content_exceeds_max_length": "Texten får inte innehålla fler än {maxLength, number} tecken.",
|
||||
"limits.integer_out_of_int64_range": "Heltalsvärdet ligger utanför det giltiga int64-intervallet.",
|
||||
"limits.rows_required": "`rows` är obligatoriskt.",
|
||||
"limits.string_length_exact": "Strängen måste vara exakt {length, plural, one {# tecken} other {# tecken}} lång.",
|
||||
"limits.string_length_invalid": "Stränglängden måste vara mellan {min} och {max} tecken.",
|
||||
"limits.synced_preferences_too_large": "Synkroniserade inställningar överskrider {max_bytes} byte.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "ไม่สามารถอัปเดตทรัพยากรได้ โปรดลองอีกครั้ง",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "ผู้ใช้ต้องเป็นบอทจึงจะสามารถระบุว่าเป็นผู้ใช้ระบบได้",
|
||||
"age_verification.already_verified": "คุณยืนยันอายุเรียบร้อยแล้ว",
|
||||
"attachments_and_uploads.attachment_fields_required": "ต้องระบุ `attachment_id`, `channel_id`, `message_id` และ `expires_at`",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` และ `message_id` ต้องเป็นจำนวนเต็มที่ถูกต้อง",
|
||||
"attachments_and_uploads.attachment_must_be_image": "ไฟล์แนบ \"{filename}\" ต้องเป็นไฟล์รูปภาพ (png, jpg, jpeg, webp หรือ gif)",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "ไม่พบไฟล์แนบ ID {attachmentId} ในข้อความ",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "ข้อความนี้ไม่อนุญาตให้แนบไฟล์",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "ไม่พบสื่อที่ถูกต้องในข้อความ",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "ภาพตัวอย่างต้องเป็นไฟล์ JPEG",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "ไม่พบไฟล์แนบที่อ้างอิง \"{filename}\" ในไฟล์แนบของข้อความ",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` ต้องเป็นจำนวนเต็มที่ถูกต้อง",
|
||||
"attachments_and_uploads.too_many_files": "ไฟล์มากเกินไป อนุญาตสูงสุด {maxFiles, plural, other {# ไฟล์}}",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "ตรวจพบ URL `attachment://` ที่ยังไม่ได้แปลงค่า ควรแปลงค่าก่อนประมวลผลเอมเบด",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "ไม่พบไฟล์แนบที่อัปโหลด {filename}",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "คุณใช้อิโมจิครบ {count} {count, plural, other {ตัว}} แล้ว",
|
||||
"limits.at_least_one_entry_required": "ต้องมีอย่างน้อยหนึ่งรายการ",
|
||||
"limits.base64_length_invalid": "ความยาวสตริง Base64 ต้องอยู่ระหว่าง {min} ถึง {maxLength} ตัวอักษร",
|
||||
"limits.bucket_required": "ต้องระบุ `bucket`",
|
||||
"limits.cannot_shrink_reserved_slots": "คุณไม่สามารถลดจำนวนช่องที่จองไว้ได้",
|
||||
"limits.cannot_specify_before_and_after": "คุณไม่สามารถระบุทั้ง `before` และ `after` ได้",
|
||||
"limits.content_exceeds_max_length": "ข้อความต้องไม่เกิน {maxLength} {maxLength, plural, other {ตัวอักษร}}",
|
||||
"limits.integer_out_of_int64_range": "ค่าจำนวนเต็มอยู่นอกช่วง int64 ที่ถูกต้อง",
|
||||
"limits.rows_required": "ต้องระบุ `rows`",
|
||||
"limits.string_length_exact": "สตริงต้องมีความยาว {length, plural, other {# ตัวอักษร}}พอดี",
|
||||
"limits.string_length_invalid": "ความยาวสตริงต้องอยู่ระหว่าง {min} ถึง {max} ตัวอักษร",
|
||||
"limits.synced_preferences_too_large": "การตั้งค่าที่ซิงค์มีขนาดเกิน {max_bytes} ไบต์",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Kaynağı güncelleyemedik. Tekrar dene.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Kullanıcı, sistem kullanıcısı olarak işaretlenmek için bot olmalı.",
|
||||
"age_verification.already_verified": "Yaş doğrulamasını zaten tamamladın.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` ve `expires_at` gerekli.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` ve `message_id` geçerli tam sayı olmalı.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "\"{filename}\" eki görsel bir dosya olmalı (png, jpg, jpeg, webp veya gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Kimliği {attachmentId} olan ek mesajda bulunamadı.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Bu mesaja ek ekleyemezsin.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Mesajda geçerli medya yok.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Önizleme JPEG olmalı.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Referans gösterilen \"{filename}\" eki mesaj eklerinde bulunamadı.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` geçerli bir tam sayı olmalı.",
|
||||
"attachments_and_uploads.too_many_files": "Çok fazla dosya. En fazla {maxFiles, plural, one {# dosya} other {# dosya}} yükleyebilirsin.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Çözülmemiş `attachment://` URL'si algılandı. Bu, gömülü içerik işlenmeden önce çözülmeliydi.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Yüklenen {filename} eki bulunamadı.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "{count, plural, one {# emoji} other {# emoji}} sınırına ulaştın.",
|
||||
"limits.at_least_one_entry_required": "En az bir öğe gerekli.",
|
||||
"limits.base64_length_invalid": "Base64 dize uzunluğu {min} ile {maxLength} karakter arasında olmalı.",
|
||||
"limits.bucket_required": "`bucket` gerekli.",
|
||||
"limits.cannot_shrink_reserved_slots": "Ayrılmış yuvaları küçültemezsin.",
|
||||
"limits.cannot_specify_before_and_after": "Hem `before` hem de `after` belirtemezsin.",
|
||||
"limits.content_exceeds_max_length": "Metin {maxLength, plural, one {# karakteri} other {# karakteri}} geçemez.",
|
||||
"limits.integer_out_of_int64_range": "Tam sayı değeri geçerli int64 aralığında değil.",
|
||||
"limits.rows_required": "`rows` gerekli.",
|
||||
"limits.string_length_exact": "Dize tam olarak {length, plural, one {# karakter} other {# karakter}} olmalı.",
|
||||
"limits.string_length_invalid": "Dize uzunluğu {min} ile {max} karakter arasında olmalı.",
|
||||
"limits.synced_preferences_too_large": "Senkronize edilen tercihler {max_bytes} baytı aşıyor.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Не вдалося оновити ресурс. Спробуй ще раз.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Користувач має бути ботом, щоб його можна було позначити як системного користувача.",
|
||||
"age_verification.already_verified": "Перевірку віку вже пройдено.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` та `expires_at` є обов'язковими.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` та `message_id` мають бути дійсними цілими числами.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Вкладення \"{filename}\" має бути файлом зображення (png, jpg, jpeg, webp або gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Вкладення з ID {attachmentId} не знайдено в повідомленні.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Для цього повідомлення вкладення заборонені.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "У повідомленні не знайдено коректних медіафайлів.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Попередній перегляд має бути у форматі JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Вказане вкладення \"{filename}\" не знайдено у вкладеннях повідомлення.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` має бути коректним цілим числом.",
|
||||
"attachments_and_uploads.too_many_files": "Забагато файлів. Дозволено максимум {maxFiles} {maxFiles, plural, one {файл} few {файли} many {файлів} other {файла}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Виявлено нерозв'язану URL-адресу `attachment://`. Її потрібно було розв'язати до обробки ембедів.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Завантажене вкладення {filename} не знайдено.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Досягнуто максимуму: {count} {count, plural, one {емодзі} few {емодзі} many {емодзі} other {емодзі}}.",
|
||||
"limits.at_least_one_entry_required": "Потрібен принаймні один запис.",
|
||||
"limits.base64_length_invalid": "Довжина рядка Base64 має бути від {min} до {maxLength} символів.",
|
||||
"limits.bucket_required": "`bucket` обов'язковий.",
|
||||
"limits.cannot_shrink_reserved_slots": "Ти не можеш зменшити кількість зарезервованих слотів.",
|
||||
"limits.cannot_specify_before_and_after": "Ти не можеш вказати одночасно `before` та `after`.",
|
||||
"limits.content_exceeds_max_length": "Текст не повинен перевищувати {maxLength} {maxLength, plural, one {символ} few {символи} many {символів} other {символа}}.",
|
||||
"limits.integer_out_of_int64_range": "Ціле значення виходить за межі допустимого діапазону int64.",
|
||||
"limits.rows_required": "`rows` обов'язковий.",
|
||||
"limits.string_length_exact": "Рядок має містити рівно {length, plural, one {# символ} few {# символи} many {# символів} other {# символа}}.",
|
||||
"limits.string_length_invalid": "Довжина рядка має бути від {min} до {max} символів.",
|
||||
"limits.synced_preferences_too_large": "Синхронізовані налаштування перевищують {max_bytes} байт.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "Không thể cập nhật tài nguyên. Vui lòng thử lại.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "Người dùng phải là bot mới được đánh dấu là người dùng hệ thống.",
|
||||
"age_verification.already_verified": "Bạn đã hoàn tất xác minh tuổi.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id` và `expires_at` là bắt buộc.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id` và `message_id` phải là các số nguyên hợp lệ.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Tệp đính kèm \"{filename}\" phải là tệp hình ảnh (png, jpg, jpeg, webp hoặc gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Không tìm thấy tệp đính kèm có ID {attachmentId} trong tin nhắn.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Tin nhắn này không cho phép tệp đính kèm.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "Không tìm thấy media hợp lệ nào trong tin nhắn.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Ảnh xem trước phải ở định dạng JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Không tìm thấy tệp đính kèm tham chiếu \"{filename}\" trong tệp đính kèm của tin nhắn.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` phải là số nguyên hợp lệ.",
|
||||
"attachments_and_uploads.too_many_files": "Quá nhiều tệp. Chỉ cho phép tối đa {maxFiles} {maxFiles, plural, other {tệp}}.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Phát hiện URL `attachment://` chưa được phân giải. URL này lẽ ra phải được phân giải trước khi xử lý embed.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Không tìm thấy tệp đính kèm đã tải lên {filename}.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "Bạn đã đạt tối đa {count} {count, plural, other {emoji}}.",
|
||||
"limits.at_least_one_entry_required": "Cần ít nhất một mục.",
|
||||
"limits.base64_length_invalid": "Độ dài chuỗi Base64 phải từ {min} đến {maxLength} ký tự.",
|
||||
"limits.bucket_required": "`bucket` là bắt buộc.",
|
||||
"limits.cannot_shrink_reserved_slots": "Bạn không thể giảm số lượng vị trí đã đặt trước.",
|
||||
"limits.cannot_specify_before_and_after": "Bạn không thể chỉ định đồng thời `before` và `after`.",
|
||||
"limits.content_exceeds_max_length": "Văn bản không được dài quá {maxLength} {maxLength, plural, other {ký tự}}.",
|
||||
"limits.integer_out_of_int64_range": "Giá trị số nguyên nằm ngoài phạm vi int64 hợp lệ.",
|
||||
"limits.rows_required": "`rows` là bắt buộc.",
|
||||
"limits.string_length_exact": "Chuỗi phải có đúng {length, plural, other {# ký tự}}.",
|
||||
"limits.string_length_invalid": "Độ dài chuỗi phải từ {min} đến {max} ký tự.",
|
||||
"limits.synced_preferences_too_large": "Tùy chọn đồng bộ hóa vượt quá {max_bytes} byte.",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "无法更新资源。请重试。",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "用户必须是机器人才能被标记为系统用户。",
|
||||
"age_verification.already_verified": "你已完成年龄验证。",
|
||||
"attachments_and_uploads.attachment_fields_required": "需要提供 `attachment_id`、`channel_id`、`message_id` 和 `expires_at`。",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`、`channel_id` 和 `message_id` 必须是有效的整数。",
|
||||
"attachments_and_uploads.attachment_must_be_image": "附件“{filename}”必须是图片文件(png、jpg、jpeg、webp 或 gif)。",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "消息中未找到 ID 为 {attachmentId} 的附件。",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "此消息不允许添加附件。",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "消息中未发现有效媒体。",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "预览必须是 JPEG。",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "消息附件中未找到引用的附件“{filename}”。",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` 必须为有效整数。",
|
||||
"attachments_and_uploads.too_many_files": "文件过多。最多允许 {maxFiles, plural, other {# 个文件}}。",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "检测到未解析的 `attachment://` URL。此 URL 应在嵌入处理前解析。",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "未找到上传的附件 {filename}。",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "你已达到 {count, plural, other {# 个表情}}的上限。",
|
||||
"limits.at_least_one_entry_required": "至少需要一个条目。",
|
||||
"limits.base64_length_invalid": "Base64 字符串长度必须在 {min} 到 {maxLength} 个字符之间。",
|
||||
"limits.bucket_required": "需要提供 `bucket`。",
|
||||
"limits.cannot_shrink_reserved_slots": "你不能减少预留槽位数量。",
|
||||
"limits.cannot_specify_before_and_after": "你不能同时指定 `before` 和 `after`。",
|
||||
"limits.content_exceeds_max_length": "文本不能超过 {maxLength, plural, other {# 个字符}}。",
|
||||
"limits.integer_out_of_int64_range": "整数值超出有效的 int64 范围。",
|
||||
"limits.rows_required": "需要提供 `rows`。",
|
||||
"limits.string_length_exact": "字符串必须正好包含 {length, plural, other {# 个字符}}。",
|
||||
"limits.string_length_invalid": "字符串长度必须在 {min} 到 {max} 个字符之间。",
|
||||
"limits.synced_preferences_too_large": "同步偏好设置已超出 {max_bytes} 字节。",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "我們無法更新資源。請再試一次。",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "使用者必須是機器人才能標記為系統使用者。",
|
||||
"age_verification.already_verified": "你已完成年齡驗證。",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`、`channel_id`、`message_id` 和 `expires_at` 為必填。",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`、`channel_id` 和 `message_id` 必須為有效的整數。",
|
||||
"attachments_and_uploads.attachment_must_be_image": "附件「{filename}」必須是圖片檔案(png、jpg、jpeg、webp 或 gif)。",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "在訊息中找不到 ID 為 {attachmentId} 的附件。",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "此訊息不允許附件。",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "在訊息中找不到有效的媒體。",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "預覽必須是 JPEG 格式。",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "在訊息附件中找不到參照的附件「{filename}」。",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` 必須是有效的整數。",
|
||||
"attachments_and_uploads.too_many_files": "檔案過多。最多允許 {maxFiles, plural, other {# 個檔案}}。",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "偵測到未解析的 `attachment://` URL。這應該在嵌入處理之前完成解析。",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "找不到已上傳的附件 {filename}。",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "你已達到 {count, plural, other {# 個表情符號}}的上限。",
|
||||
"limits.at_least_one_entry_required": "至少需要一個項目。",
|
||||
"limits.base64_length_invalid": "Base64 字串長度必須介於 {min} 到 {maxLength} 個字元之間。",
|
||||
"limits.bucket_required": "需要 `bucket`。",
|
||||
"limits.cannot_shrink_reserved_slots": "你無法減少已保留的名額。",
|
||||
"limits.cannot_specify_before_and_after": "你無法同時指定 `before` 和 `after`。",
|
||||
"limits.content_exceeds_max_length": "文字不得超過 {maxLength, plural, other {# 個字元}}。",
|
||||
"limits.integer_out_of_int64_range": "整數值超出有效的 int64 範圍。",
|
||||
"limits.rows_required": "需要 `rows`。",
|
||||
"limits.string_length_exact": "字串必須正好包含 {length, plural, other {# 個字元}}。",
|
||||
"limits.string_length_invalid": "字串長度必須介於 {min} 到 {max} 個字元之間。",
|
||||
"limits.synced_preferences_too_large": "同步的偏好設定超過 {max_bytes} 位元組。",
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
"admin_and_system.update_failed": "We couldn't update the resource. Please try again.",
|
||||
"admin_and_system.user_must_be_bot_for_system_user": "User must be a bot to be marked as a system user.",
|
||||
"age_verification.already_verified": "You've already completed age verification.",
|
||||
"attachments_and_uploads.attachment_fields_required": "`attachment_id`, `channel_id`, `message_id`, and `expires_at` are required.",
|
||||
"attachments_and_uploads.attachment_ids_must_be_valid_integers": "`attachment_id`, `channel_id`, and `message_id` must be valid integers.",
|
||||
"attachments_and_uploads.attachment_must_be_image": "Attachment \"{filename}\" must be an image file (png, jpg, jpeg, webp, or gif).",
|
||||
"attachments_and_uploads.attachment_not_found_in_message": "Attachment with ID {attachmentId} wasn't found in the message.",
|
||||
"attachments_and_uploads.attachments_not_allowed_for_message": "Attachments aren't allowed for this message.",
|
||||
@@ -66,7 +64,6 @@
|
||||
"attachments_and_uploads.no_valid_media_in_message": "No valid media was found in the message.",
|
||||
"attachments_and_uploads.preview_must_be_jpeg": "Preview must be JPEG.",
|
||||
"attachments_and_uploads.referenced_attachment_not_found": "Referenced attachment \"{filename}\" wasn't found in message attachments.",
|
||||
"attachments_and_uploads.size_bytes_must_be_valid_integer": "`size_bytes` must be a valid integer.",
|
||||
"attachments_and_uploads.too_many_files": "Too many files. Maximum {maxFiles, plural, one {# file} other {# files}} allowed.",
|
||||
"attachments_and_uploads.unresolved_attachment_url": "Unresolved `attachment://` URL detected. This should have been resolved before embed processing.",
|
||||
"attachments_and_uploads.uploaded_attachment_not_found": "Uploaded attachment {filename} wasn't found.",
|
||||
@@ -296,12 +293,10 @@
|
||||
"invites_and_packs.max_emojis_reached": "You've reached the maximum of {count, plural, one {# emoji} other {# emojis}}.",
|
||||
"limits.at_least_one_entry_required": "At least one entry is required.",
|
||||
"limits.base64_length_invalid": "Base64 string length must be between {min} and {maxLength} characters.",
|
||||
"limits.bucket_required": "`bucket` is required.",
|
||||
"limits.cannot_shrink_reserved_slots": "You can't shrink reserved slots.",
|
||||
"limits.cannot_specify_before_and_after": "You can't specify both `before` and `after`.",
|
||||
"limits.content_exceeds_max_length": "Text must not exceed {maxLength, plural, one {# character} other {# characters}}.",
|
||||
"limits.integer_out_of_int64_range": "Integer value is out of the valid int64 range.",
|
||||
"limits.rows_required": "`rows` is required.",
|
||||
"limits.string_length_exact": "String must be exactly {length, plural, one {# character} other {# characters}}.",
|
||||
"limits.string_length_invalid": "String length must be between {min} and {max} characters.",
|
||||
"limits.synced_preferences_too_large": "Synced preferences exceed {max_bytes} bytes.",
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {createErrorHandler as createFluxerErrorHandler} from '@fluxer/errors/src/ErrorHandler';
|
||||
import {resolveRoutePattern} from '@fluxer/errors/src/error_handling/RoutePattern';
|
||||
import type {Context, ErrorHandler} from 'hono';
|
||||
|
||||
export interface ErrorHandlerOptions {
|
||||
includeStack?: boolean;
|
||||
logger?: (error: Error, context: Context) => void;
|
||||
captureException?: (error: Error, context?: Record<string, unknown>) => void;
|
||||
}
|
||||
|
||||
export function createErrorHandler(options: ErrorHandlerOptions = {}): ErrorHandler {
|
||||
const {includeStack = false, logger, captureException} = options;
|
||||
const logError =
|
||||
logger || captureException
|
||||
? (error: Error, context: Context) => {
|
||||
if (logger) {
|
||||
logger(error, context);
|
||||
}
|
||||
if (captureException) {
|
||||
captureException(error, {
|
||||
path: resolveRoutePattern(context),
|
||||
method: context.req.method,
|
||||
status: context.res?.status,
|
||||
});
|
||||
}
|
||||
}
|
||||
: undefined;
|
||||
return createFluxerErrorHandler({
|
||||
includeStack,
|
||||
logError,
|
||||
});
|
||||
}
|
||||
@@ -4,14 +4,8 @@ import type {CacheHeadersOptions} from '@fluxer/hono/src/middleware/CacheHeaders
|
||||
import {cacheHeaders} from '@fluxer/hono/src/middleware/CacheHeaders';
|
||||
import type {CorsOptions} from '@fluxer/hono/src/middleware/Cors';
|
||||
import {cors} from '@fluxer/hono/src/middleware/Cors';
|
||||
import type {ErrorHandlerOptions} from '@fluxer/hono/src/middleware/ErrorHandler';
|
||||
import {createErrorHandler} from '@fluxer/hono/src/middleware/ErrorHandler';
|
||||
import type {RateLimitOptions, RateLimitService} from '@fluxer/hono/src/middleware/RateLimit';
|
||||
import {rateLimit} from '@fluxer/hono/src/middleware/RateLimit';
|
||||
import type {RequestIdOptions} from '@fluxer/hono/src/middleware/RequestId';
|
||||
import {requestId} from '@fluxer/hono/src/middleware/RequestId';
|
||||
import type {LogFunction, RequestLoggerOptions} from '@fluxer/hono/src/middleware/RequestLogger';
|
||||
import {requestLogger} from '@fluxer/hono/src/middleware/RequestLogger';
|
||||
import {fluxerVersionHeader} from '@fluxer/hono/src/middleware/VersionHeader';
|
||||
import type {Env, Hono, MiddlewareHandler} from 'hono';
|
||||
|
||||
@@ -19,26 +13,9 @@ interface MiddlewareStackOptions {
|
||||
requestId?: RequestIdOptions;
|
||||
cors?: CorsOptions;
|
||||
cacheHeaders?: CacheHeadersOptions | false;
|
||||
logger?: Omit<RequestLoggerOptions, 'log'> & {
|
||||
log?: LogFunction;
|
||||
};
|
||||
rateLimit?: RateLimitOptions & {
|
||||
service?: RateLimitService;
|
||||
};
|
||||
errorHandler?: ErrorHandlerOptions;
|
||||
customMiddleware?: Array<MiddlewareHandler>;
|
||||
}
|
||||
|
||||
interface ApplyMiddlewareStackOptions extends MiddlewareStackOptions {
|
||||
skipRequestId?: boolean;
|
||||
skipCors?: boolean;
|
||||
skipCacheHeaders?: boolean;
|
||||
skipLogger?: boolean;
|
||||
skipRateLimit?: boolean;
|
||||
skipErrorHandler?: boolean;
|
||||
}
|
||||
|
||||
function createStandardMiddlewareStack(options: MiddlewareStackOptions = {}): Array<MiddlewareHandler> {
|
||||
function createStandardMiddlewareStack(options: MiddlewareStackOptions): Array<MiddlewareHandler> {
|
||||
const stack: Array<MiddlewareHandler> = [fluxerVersionHeader()];
|
||||
if (options.requestId) {
|
||||
stack.push(requestId(options.requestId));
|
||||
@@ -49,44 +26,11 @@ function createStandardMiddlewareStack(options: MiddlewareStackOptions = {}): Ar
|
||||
if (options.cacheHeaders !== false) {
|
||||
stack.push(cacheHeaders(options.cacheHeaders ?? {}));
|
||||
}
|
||||
if (options.logger?.log) {
|
||||
stack.push(
|
||||
requestLogger({
|
||||
log: options.logger.log,
|
||||
skip: options.logger.skip,
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (options.rateLimit && options.rateLimit.enabled !== false && options.rateLimit.service) {
|
||||
stack.push(rateLimit(options.rateLimit));
|
||||
}
|
||||
if (options.customMiddleware) {
|
||||
stack.push(...options.customMiddleware);
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
function buildStackOptions(options: ApplyMiddlewareStackOptions): MiddlewareStackOptions {
|
||||
return {
|
||||
requestId: options.skipRequestId ? undefined : options.requestId,
|
||||
cors: options.skipCors ? undefined : options.cors,
|
||||
cacheHeaders: options.skipCacheHeaders ? false : options.cacheHeaders,
|
||||
logger: options.skipLogger ? undefined : options.logger,
|
||||
rateLimit: options.skipRateLimit ? undefined : options.rateLimit,
|
||||
customMiddleware: options.customMiddleware,
|
||||
};
|
||||
}
|
||||
|
||||
export function applyMiddlewareStack<E extends Env = Env>(
|
||||
app: Hono<E>,
|
||||
options: ApplyMiddlewareStackOptions = {},
|
||||
): void {
|
||||
const stack = createStandardMiddlewareStack(buildStackOptions(options));
|
||||
for (const middleware of stack) {
|
||||
export function applyMiddlewareStack<E extends Env = Env>(app: Hono<E>, options: MiddlewareStackOptions = {}): void {
|
||||
for (const middleware of createStandardMiddlewareStack(options)) {
|
||||
app.use('*', middleware);
|
||||
}
|
||||
if (!options.skipErrorHandler) {
|
||||
const errorHandler = createErrorHandler(options.errorHandler ?? {});
|
||||
app.onError(errorHandler);
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {matchesAnyPathPattern} from '@fluxer/hono/src/middleware/utils/PathMatchers';
|
||||
import {extractClientIp} from '@fluxer/ip_utils/src/ClientIp';
|
||||
import type {MiddlewareHandler} from 'hono';
|
||||
|
||||
export interface RateLimitResult {
|
||||
allowed: boolean;
|
||||
limit: number;
|
||||
remaining: number;
|
||||
resetTime: Date;
|
||||
retryAfter?: number;
|
||||
}
|
||||
|
||||
export interface RateLimitService {
|
||||
checkLimit(params: {identifier: string; maxAttempts: number; windowMs: number}): Promise<RateLimitResult>;
|
||||
}
|
||||
|
||||
type KeyGenerator = (request: Request) => string | null | Promise<string | null>;
|
||||
|
||||
export interface RateLimitOptions {
|
||||
enabled?: boolean;
|
||||
skipPaths?: Array<string>;
|
||||
service?: RateLimitService;
|
||||
maxAttempts?: number;
|
||||
windowMs?: number;
|
||||
keyGenerator?: KeyGenerator;
|
||||
onLimitExceeded?: (identifier: string, path: string) => void;
|
||||
trustClientIpHeader?: boolean;
|
||||
clientIpHeaderName?: string;
|
||||
}
|
||||
|
||||
function getClientIp(req: Request, trustClientIpHeader?: boolean, clientIpHeaderName?: string): string | null {
|
||||
return extractClientIp(req, {trustClientIpHeader, clientIpHeaderName});
|
||||
}
|
||||
|
||||
function createDefaultKeyGenerator(trustClientIpHeader?: boolean, clientIpHeaderName?: string): KeyGenerator {
|
||||
return function defaultKeyGenerator(req: Request): string | null {
|
||||
return getClientIp(req, trustClientIpHeader, clientIpHeaderName);
|
||||
};
|
||||
}
|
||||
|
||||
export function rateLimit(options: RateLimitOptions = {}): MiddlewareHandler {
|
||||
const {
|
||||
enabled = true,
|
||||
skipPaths = ['/_health', '/metrics'],
|
||||
service,
|
||||
maxAttempts = 100,
|
||||
windowMs = 60000,
|
||||
keyGenerator,
|
||||
onLimitExceeded,
|
||||
trustClientIpHeader = false,
|
||||
clientIpHeaderName,
|
||||
} = options;
|
||||
const resolvedKeyGenerator = keyGenerator ?? createDefaultKeyGenerator(trustClientIpHeader, clientIpHeaderName);
|
||||
return async (c, next) => {
|
||||
if (!enabled || !service) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
const path = c.req.path;
|
||||
if (matchesAnyPathPattern(path, skipPaths)) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
const identifier = await resolvedKeyGenerator(c.req.raw);
|
||||
if (!identifier) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
const result = await service.checkLimit({
|
||||
identifier,
|
||||
maxAttempts,
|
||||
windowMs,
|
||||
});
|
||||
c.header('X-RateLimit-Limit', result.limit.toString());
|
||||
c.header('X-RateLimit-Remaining', result.remaining.toString());
|
||||
c.header('X-RateLimit-Reset', Math.floor(result.resetTime.getTime() / 1000).toString());
|
||||
if (!result.allowed) {
|
||||
if (result.retryAfter !== undefined) {
|
||||
c.header('Retry-After', result.retryAfter.toString());
|
||||
}
|
||||
if (onLimitExceeded) {
|
||||
onLimitExceeded(identifier, path);
|
||||
}
|
||||
return c.json(
|
||||
{
|
||||
error: 'Too Many Requests',
|
||||
message: 'Rate limit exceeded',
|
||||
retryAfter: result.retryAfter,
|
||||
},
|
||||
429,
|
||||
);
|
||||
}
|
||||
await next();
|
||||
return;
|
||||
};
|
||||
}
|
||||
@@ -5,27 +5,6 @@ import {Hono} from 'hono';
|
||||
import {describe, expect, test} from 'vitest';
|
||||
|
||||
describe('CORS Middleware', () => {
|
||||
describe('wildcard origin', () => {
|
||||
test('sets Access-Control-Allow-Origin to * for wildcard origins', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors({origins: '*'}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test', {
|
||||
headers: {origin: 'https://example.com'},
|
||||
});
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*');
|
||||
});
|
||||
test('does not set Vary header for wildcard origins', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors({origins: '*'}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test', {
|
||||
headers: {origin: 'https://example.com'},
|
||||
});
|
||||
expect(response.headers.get('Vary')).toBeNull();
|
||||
});
|
||||
});
|
||||
describe('specific origins', () => {
|
||||
test('allows requests from whitelisted origin', async () => {
|
||||
const app = new Hono();
|
||||
@@ -49,158 +28,5 @@ describe('CORS Middleware', () => {
|
||||
expect(response.headers.get('Access-Control-Allow-Origin')).toBeNull();
|
||||
expect(response.headers.get('Vary')).toBe('Origin');
|
||||
});
|
||||
test('does not set origin header when no origin header in request', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors({origins: ['https://allowed.com']}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get('Access-Control-Allow-Origin')).toBeNull();
|
||||
});
|
||||
});
|
||||
describe('credentials', () => {
|
||||
test('sets Access-Control-Allow-Credentials when credentials is true', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors({credentials: true}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test');
|
||||
expect(response.headers.get('Access-Control-Allow-Credentials')).toBe('true');
|
||||
});
|
||||
test('does not set Access-Control-Allow-Credentials when credentials is false', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors({credentials: false}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test');
|
||||
expect(response.headers.get('Access-Control-Allow-Credentials')).toBeNull();
|
||||
});
|
||||
});
|
||||
describe('preflight requests (OPTIONS)', () => {
|
||||
test('responds to preflight with 204 status', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors());
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test', {
|
||||
method: 'OPTIONS',
|
||||
headers: {origin: 'https://example.com'},
|
||||
});
|
||||
expect(response.status).toBe(204);
|
||||
});
|
||||
test('sets Access-Control-Allow-Methods header on preflight', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors({methods: ['GET', 'POST', 'PUT']}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test', {
|
||||
method: 'OPTIONS',
|
||||
});
|
||||
expect(response.headers.get('Access-Control-Allow-Methods')).toBe('GET, POST, PUT');
|
||||
});
|
||||
test('uses default methods when not specified', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors());
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test', {
|
||||
method: 'OPTIONS',
|
||||
});
|
||||
const methods = response.headers.get('Access-Control-Allow-Methods');
|
||||
expect(methods).toContain('GET');
|
||||
expect(methods).toContain('POST');
|
||||
expect(methods).toContain('PUT');
|
||||
expect(methods).toContain('DELETE');
|
||||
expect(methods).toContain('OPTIONS');
|
||||
});
|
||||
test('sets Access-Control-Allow-Headers header on preflight', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors({allowedHeaders: ['Content-Type', 'Authorization', 'X-Custom-Header']}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test', {
|
||||
method: 'OPTIONS',
|
||||
});
|
||||
expect(response.headers.get('Access-Control-Allow-Headers')).toBe('Content-Type, Authorization, X-Custom-Header');
|
||||
});
|
||||
test('sets Access-Control-Max-Age header on preflight', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors({maxAge: 3600}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test', {
|
||||
method: 'OPTIONS',
|
||||
});
|
||||
expect(response.headers.get('Access-Control-Max-Age')).toBe('3600');
|
||||
});
|
||||
test('uses default maxAge of 86400', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors());
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test', {
|
||||
method: 'OPTIONS',
|
||||
});
|
||||
expect(response.headers.get('Access-Control-Max-Age')).toBe('86400');
|
||||
});
|
||||
});
|
||||
describe('exposed headers', () => {
|
||||
test('sets Access-Control-Expose-Headers when exposedHeaders is provided', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors({exposedHeaders: ['X-Custom-Header', 'X-Another-Header']}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test');
|
||||
expect(response.headers.get('Access-Control-Expose-Headers')).toBe('X-Custom-Header, X-Another-Header');
|
||||
});
|
||||
test('does not set Access-Control-Expose-Headers when empty', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors({exposedHeaders: []}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test');
|
||||
expect(response.headers.get('Access-Control-Expose-Headers')).toBeNull();
|
||||
});
|
||||
test('sets exposed headers on both preflight and regular requests', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors({exposedHeaders: ['X-Custom-Header']}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const preflightResponse = await app.request('/test', {method: 'OPTIONS'});
|
||||
expect(preflightResponse.headers.get('Access-Control-Expose-Headers')).toBe('X-Custom-Header');
|
||||
const regularResponse = await app.request('/test');
|
||||
expect(regularResponse.headers.get('Access-Control-Expose-Headers')).toBe('X-Custom-Header');
|
||||
});
|
||||
});
|
||||
describe('enabled option', () => {
|
||||
test('skips CORS when enabled is false', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors({enabled: false}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test', {
|
||||
headers: {origin: 'https://example.com'},
|
||||
});
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get('Access-Control-Allow-Origin')).toBeNull();
|
||||
});
|
||||
test('processes CORS when enabled is true', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors({enabled: true, origins: '*'}));
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test', {
|
||||
headers: {origin: 'https://example.com'},
|
||||
});
|
||||
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*');
|
||||
});
|
||||
test('enabled defaults to true', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors());
|
||||
app.get('/test', (c) => c.json({ok: true}));
|
||||
const response = await app.request('/test', {
|
||||
headers: {origin: 'https://example.com'},
|
||||
});
|
||||
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*');
|
||||
});
|
||||
});
|
||||
describe('integration with routes', () => {
|
||||
test('works with multiple routes', async () => {
|
||||
const app = new Hono();
|
||||
app.use('*', cors({origins: '*'}));
|
||||
app.get('/api/users', (c) => c.json({users: []}));
|
||||
app.post('/api/users', (c) => c.json({created: true}));
|
||||
const getResponse = await app.request('/api/users');
|
||||
expect(getResponse.headers.get('Access-Control-Allow-Origin')).toBe('*');
|
||||
const postResponse = await app.request('/api/users', {method: 'POST'});
|
||||
expect(postResponse.headers.get('Access-Control-Allow-Origin')).toBe('*');
|
||||
});
|
||||
});
|
||||
});
|
||||
Loaded 100 of 1391 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user