chore(repo): remove dead code and useless tests (#3375)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-10 21:43:29 +02:00
1 parent 7e177a1c90
commit 1cd62eb661
1391 files changed
+783 -120415

No files matched your search

-7
View File
@@ -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({
-14
View File
@@ -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';
-60
View File
@@ -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,
});
};
}
-96
View File
@@ -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('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&apos;');
}
@@ -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',
-147
View File
@@ -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.',
-5
View File
@@ -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} بايت.",
-5
View File
@@ -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} байта.",
-5
View File
@@ -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ů.",
-5
View File
@@ -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.",
-5
View File
@@ -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.",
-5
View File
@@ -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.",
-5
View File
@@ -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.",
-5
View File
@@ -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.",
-5
View File
@@ -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} בתים.",
-5
View File
@@ -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} बाइट्स से ज़्यादा हैं।",
-5
View File
@@ -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.",
-5
View File
@@ -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.",
-5
View File
@@ -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.",
-5
View File
@@ -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.",
-5
View File
@@ -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}バイトを超えています。",
-5
View File
@@ -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}바이트를 초과했어요.",
-5
View File
@@ -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.",
-5
View File
@@ -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.",
-5
View File
@@ -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.",
-5
View File
@@ -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.",
-5
View File
@@ -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.",
-5
View File
@@ -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.",
-5
View File
@@ -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} ไบต์",
-5
View File
@@ -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.",
-5
View File
@@ -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} байт.",
-5
View File
@@ -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);
}
}
-98
View File
@@ -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