From 0766778a8754a7f1b72a7893db823b71de3f93b3 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Fri, 22 Mar 2019 22:43:27 +0100 Subject: [PATCH 01/13] Improved avatar and image cache, moved to side cach instead of ItemStorage --- shared/js/FileManager.ts | 587 +++++++++++++--------- shared/js/permission/PermissionManager.ts | 6 +- shared/js/ui/htmltags.ts | 6 +- test/js/client.js | 22 +- 4 files changed, 361 insertions(+), 260 deletions(-) diff --git a/shared/js/FileManager.ts b/shared/js/FileManager.ts index 262bc2f8..07054d08 100644 --- a/shared/js/FileManager.ts +++ b/shared/js/FileManager.ts @@ -5,6 +5,7 @@ FIXME: Dont use item storage with base64! Use the larger cache API and drop IE support! https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage#Browser_compatibility */ + class FileEntry { name: string; datetime: number; @@ -19,43 +20,53 @@ class FileListRequest { callback: (entries: FileEntry[]) => void; } -class DownloadFileTransfer { - transferId: number; - serverTransferId: number; - transferKey: string; +namespace transfer { + export interface DownloadKey { + client_transfer_id: number; + server_transfer_id: number; - totalSize: number; + key: string; + total_size: number; + + file_path: string; + file_name: string; + + peer: { + hosts: string[], + port: number; + }; + } +} + +class StreamedFileDownload { + readonly transfer_key: transfer.DownloadKey; currentSize: number = 0; - remotePort: number; - remoteHost: string; - on_start: () => void = () => {}; on_complete: () => void = () => {}; on_fail: (reason: string) => void = (_) => {}; on_data: (data: Uint8Array) => void = (_) => {}; private _handle: FileManager; - private _promiseCallback: (value: DownloadFileTransfer) => void; + private _promiseCallback: (value: StreamedFileDownload) => void; private _socket: WebSocket; private _active: boolean; private _succeed: boolean; private _parseActive: boolean; - constructor(handle: FileManager, id: number) { - this.transferId = id; - this._handle = handle; + constructor(key: transfer.DownloadKey) { + this.transfer_key = key; } - startTransfer() { - if(!this.remoteHost || !this.remotePort || !this.transferKey || !this.totalSize) { + start() { + if(!this.transfer_key) { this.on_fail("Missing data!"); return; } - console.debug(tr("Create new file download to %s:%s (Key: %s, Expect %d bytes)"), this.remoteHost, this.remotePort, this.transferId, this.totalSize); + console.debug(tr("Create new file download to %s:%s (Key: %s, Expect %d bytes)"), this.transfer_key.peer.hosts[0], this.transfer_key.peer.port, this.transfer_key.key, this.transfer_key.total_size); this._active = true; - this._socket = new WebSocket("wss://" + this.remoteHost + ":" + this.remotePort); + this._socket = new WebSocket("wss://" + this.transfer_key.peer.hosts[0] + ":" + this.transfer_key.peer.port); this._socket.onopen = this.onOpen.bind(this); this._socket.onclose = this.onClose.bind(this); this._socket.onmessage = this.onMessage.bind(this); @@ -65,7 +76,7 @@ class DownloadFileTransfer { private onOpen() { if(!this._active) return; - this._socket.send(this.transferKey); + this._socket.send(this.transfer_key.key); this.on_start(); } @@ -88,7 +99,7 @@ class DownloadFileTransfer { private onBinaryData(data: Uint8Array) { this.currentSize += data.length; this.on_data(data); - if(this.currentSize == this.totalSize) { + if(this.currentSize == this.transfer_key.total_size) { this._succeed = true; this.on_complete(); this.disconnect(); @@ -113,6 +124,41 @@ class DownloadFileTransfer { //this._socket.close(); } } +class RequestFileDownload { + readonly transfer_key: transfer.DownloadKey; + + constructor(key: transfer.DownloadKey) { + this.transfer_key = key; + } + + async request_file() : Promise { + return await this.try_fetch("https://" + this.transfer_key.peer.hosts[0] + ":" + this.transfer_key.peer.port); + } + + /* + response.setHeader("Access-Control-Allow-Methods", {"GET, POST"}); + response.setHeader("Access-Control-Allow-Origin", {"*"}); + response.setHeader("Access-Control-Allow-Headers", {"*"}); + response.setHeader("Access-Control-Max-Age", {"86400"}); + response.setHeader("Access-Control-Expose-Headers", {"X-media-bytes"}); + */ + private async try_fetch(url: string) : Promise { + const response = await fetch(url, { + method: 'GET', + cache: "no-cache", + mode: 'cors', + headers: { + 'transfer-key': this.transfer_key.key, + 'download-name': this.transfer_key.file_name, + 'Access-Control-Allow-Headers': '*', + 'Access-Control-Expose-Headers': '*' + } + }); + if(!response.ok) + throw (response.type == 'opaque' || response.type == 'opaqueredirect' ? "invalid cross origin flag! May target isn't a TeaSpeak server?" : response.statusText || "response is not ok"); + return response; + } +} class FileManager extends connection.AbstractCommandHandler { handle: TSClient; @@ -120,7 +166,7 @@ class FileManager extends connection.AbstractCommandHandler { avatars: AvatarManager; private listRequests: FileListRequest[] = []; - private pendingDownloadTransfers: DownloadFileTransfer[] = []; + private pendingDownloadTransfers: transfer.DownloadKey[] = []; private downloadCounter : number = 0; constructor(client: TSClient) { @@ -211,20 +257,26 @@ class FileManager extends connection.AbstractCommandHandler { /******************************** File download ********************************/ - requestFileDownload(path: string, file: string, channel?: ChannelEntry, password?: string) : Promise { + download_file(path: string, file: string, channel?: ChannelEntry, password?: string) : Promise { const _this = this; - let transfer = new DownloadFileTransfer(this, this.downloadCounter++); - this.pendingDownloadTransfers.push(transfer); - return new Promise((resolve, reject) => { - transfer["_promiseCallback"] = resolve; + + const transfer_data: transfer.DownloadKey = { + file_name: file, + file_path: file, + client_transfer_id: this.downloadCounter++ + } as any; + + this.pendingDownloadTransfers.push(transfer_data); + return new Promise((resolve, reject) => { + transfer_data["_promiseCallback"] = resolve; _this.handle.serverConnection.send_command("ftinitdownload", { "path": path, "name": file, "cid": (channel ? channel.channelId : "0"), "cpw": (password ? password : ""), - "clientftfid": transfer.transferId + "clientftfid": transfer_data.client_transfer_id }).catch(reason => { - _this.pendingDownloadTransfers.remove(transfer); + _this.pendingDownloadTransfers.remove(transfer_data); reject(reason); }) }); @@ -233,31 +285,36 @@ class FileManager extends connection.AbstractCommandHandler { private notifyStartDownload(json) { json = json[0]; - let transfer: DownloadFileTransfer; + let transfer: transfer.DownloadKey; for(let e of this.pendingDownloadTransfers) - if(e.transferId == json["clientftfid"]) { + if(e.client_transfer_id == json["clientftfid"]) { transfer = e; break; } - transfer.serverTransferId = json["serverftfid"]; - transfer.transferKey = json["ftkey"]; - transfer.totalSize = json["size"]; + transfer.server_transfer_id = json["serverftfid"]; + transfer.key = json["ftkey"]; + transfer.total_size = json["size"]; - transfer.remotePort = json["port"]; - transfer.remoteHost = (json["ip"] ? json["ip"] : "").replace(/,/g, ""); - if(!transfer.remoteHost || transfer.remoteHost == '0.0.0.0' || transfer.remoteHost == '127.168.0.0') - transfer.remoteHost = this.handle.serverConnection._remote_address.host; + transfer.peer = { + hosts: (json["ip"] || "").split(","), + port: json["port"] + }; - (transfer["_promiseCallback"] as (val: DownloadFileTransfer) => void)(transfer); + if(transfer.peer.hosts.length == 0) + transfer.peer.hosts.push("0.0.0.0"); + + if(transfer.peer.hosts[0].length == 0 || transfer.peer.hosts[0] == '0.0.0.0') + transfer.peer.hosts[0] = this.handle.serverConnection._remote_address.host; + + (transfer["_promiseCallback"] as (val: transfer.DownloadKey) => void)(transfer); this.pendingDownloadTransfers.remove(transfer); } } class Icon { id: number; - name: string; - base64: string; + url: string; } enum ImageType { @@ -305,286 +362,330 @@ function image_type(base64: string) { return ImageType.UNKNOWN; } +class CacheManager { + readonly cache_name: string; + + private _cache_category: Cache; + + constructor(name: string) { + this.cache_name = name; + } + + setupped() : boolean { return !!this._cache_category; } + + async setup() { + if(!window.caches) + throw "Missing caches!"; + + this._cache_category = await caches.open(this.cache_name); + } + + async cleanup(max_age: number) { + /* FIXME: TODO */ + } + + async resolve_cached(key: string, max_age?: number) : Promise { + max_age = typeof(max_age) === "number" ? max_age : -1; + + const request = new Request("cache_request_" + key); + const cached_response = await this._cache_category.match(request); + if(!cached_response) + return undefined; + + /* FIXME: Max age */ + return cached_response; + } + + async put_cache(key: string, value: Response, type?: string, headers?: {[key: string]:string}) { + const request = new Request("cache_request_" + key); + + const new_headers = new Headers(); + for(const key of value.headers.keys()) + new_headers.set(key, value.headers.get(key)); + if(type) + new_headers.set("Content-type", type); + for(const key of Object.keys(headers || {})) + new_headers.set(key, headers[key]); + + await this._cache_category.put(request, new Response(value.body, { + headers: new_headers + })); + } +} + class IconManager { handle: FileManager; - private loading_icons: {promise: Promise, id: number}[] = []; + private cache: CacheManager; + private _id_urls: {[id:number]:string} = {}; + private _loading_promises: {[id:number]:Promise} = {}; constructor(handle: FileManager) { this.handle = handle; + this.cache = new CacheManager("icons"); } iconList() : Promise { return this.handle.requestFileList("/icons"); } - downloadIcon(id: number) : Promise { - return this.handle.requestFileDownload("", "/icon_" + id); + create_icon_download(id: number) : Promise { + return this.handle.download_file("", "/icon_" + id); } - resolveCached?(id: number) : Icon { - let icon = localStorage.getItem("icon_" + id); - if(icon) { - let i = JSON.parse(icon) as Icon; - if(i.base64.length > 0) { //TODO timestamp? - return i; - } - } + private async _response_url(response: Response) { + if(!response.headers.has('X-media-bytes')) + throw "missing media bytes"; + + const type = image_type(response.headers.get('X-media-bytes')); + const media = media_image_type(type); + + const blob = await response.blob(); + return URL.createObjectURL(blob.slice(0, blob.size, "image/" + media)); + } + + async resolved_cached?(id: number) : Promise { + if(this._id_urls[id]) + return { + id: id, + url: this._id_urls[id] + }; + + if(!this.cache.setupped()) + await this.cache.setup(); + + const response = await this.cache.resolve_cached('icon_' + id); //TODO age! + if(response) + return { + id: id, + url: (this._id_urls[id] = await this._response_url(response)) + }; return undefined; } - private load_finished(id: number) { - for(let entry of this.loading_icons) - if(entry.id == id) - this.loading_icons.remove(entry); + private async _load_icon(id: number) : Promise { + let download_key: transfer.DownloadKey; + try { + download_key = await this.create_icon_download(id); + } catch(error) { + console.error(tr("Could not request download for icon %d: %o"), id, error); + throw "Failed to request icon"; + } + + const downloader = new RequestFileDownload(download_key); + let response: Response; + try { + response = await downloader.request_file(); + } catch(error) { + console.error(tr("Could not download icon %d: %o"), id, error); + throw "failed to download icon"; + } + + const type = image_type(response.headers.get('X-media-bytes')); + const media = media_image_type(type); + + await this.cache.put_cache('icon_' + id, response.clone(), "image/" + media); + const url = (this._id_urls[id] = await this._response_url(response.clone())); + + this._loading_promises[id] = undefined; + return { + id: id, + url: url + }; } + loadIcon(id: number) : Promise { - for(let entry of this.loading_icons) - if(entry.id == id) return entry.promise; - - let promise = new Promise((resolve, reject) => { - let icon = this.resolveCached(id); - if(icon){ - this.load_finished(id); - resolve(icon); - return; - } - - this.downloadIcon(id).then(ft => { - let array = new Uint8Array(0); - ft.on_fail = reason => { - this.load_finished(id); - console.error(tr("Could not download icon %s -> %s"), id, tr(reason)); - chat.serverChat().appendError(tr("Fail to download icon {0}. ({1})"), id, JSON.stringify(reason)); - reject(reason); - }; - ft.on_start = () => {}; - ft.on_data = (data: Uint8Array) => { - array = concatenate(Uint8Array, array, data); - }; - ft.on_complete = () => { - let base64 = btoa(String.fromCharCode.apply(null, array)); - let icon = new Icon(); - icon.base64 = base64; - icon.id = id; - icon.name = "icon_" + id; - - localStorage.setItem("icon_" + id, JSON.stringify(icon)); - this.load_finished(id); - resolve(icon); - }; - - ft.startTransfer(); - }).catch(reason => { - console.error(tr("Error while downloading icon! (%s)"), tr(JSON.stringify(reason))); - chat.serverChat().appendError(tr("Failed to request download for icon {0}. ({1})"), id, tr(JSON.stringify(reason))); - reject(reason); - }); - }); - - this.loading_icons.push({promise: promise, id: id}); - return promise; + return this._loading_promises[id] || (this._loading_promises[id] = this._load_icon(id)); } - //$("\"tick\"") generateTag(id: number) : JQuery { if(id == 0) return $.spawn("div").addClass("icon_empty"); else if(id < 1000) return $.spawn("div").addClass("icon client-group_" + id); - let tag = $.spawn("div"); - tag.addClass("icon-container icon_empty"); - let img = $.spawn("img"); - img.attr("width", 16).attr("height", 16).attr("alt", ""); + const icon_container = $.spawn("div").addClass("icon-container icon_empty"); + const icon_image = $.spawn("img").attr("width", 16).attr("height", 16).attr("alt", ""); - let icon = this.resolveCached(id); - if(icon) { - const type = image_type(icon.base64); - const media = media_image_type(type); - console.debug(tr("Icon has an image type of %o (media: %o)"), type, media); - img.attr("src", "data:image/" + media + ";base64," + icon.base64); - tag.append(img).removeClass("icon_empty"); + if(this._id_urls[id]) { + icon_image.attr("src", this._id_urls[id]).appendTo(icon_container); + icon_container.removeClass("icon_empty"); } else { - img.attr("src", "file://null"); + const icon_load_image = $.spawn("div").addClass("icon_loading"); + icon_load_image.appendTo(icon_container); - let loader = $.spawn("div"); - loader.addClass("icon_loading"); - tag.append(loader); + (async () => { + let icon: Icon; + try { + icon = await this.resolved_cached(id); + } catch(error) { + console.error(error); + } - this.loadIcon(id).then(icon => { - const type = image_type(icon.base64); - const media = media_image_type(type); - console.debug(tr("Icon has an image type of %o (media: %o)"), type, media); - img.attr("src", "data:image/" + media + ";base64," + icon.base64); - console.debug(tr("Icon %o loaded :)"), id); + if(!icon) + icon = await this.loadIcon(id); - img.css("opacity", 0); - tag.append(img).removeClass("icon_empty"); - loader.animate({opacity: 0}, 50, function () { - $(this).detach(); - img.animate({opacity: 1}, 150); + if(!icon) + throw "failed to download icon"; + + icon_image.attr("src", icon.url); + icon_image.css("opacity", 0); + icon_container.append(icon_image).removeClass("icon_empty"); + + icon_load_image.animate({opacity: 0}, 50, function () { + icon_load_image.detach(); + icon_image.animate({opacity: 1}, 150); }); - }).catch(reason => { - console.error(tr("Could not load icon %o. Reason: %p"), id, reason); - loader.removeClass("icon_loading").addClass("icon client-warning").attr("tag", "Could not load icon " + id); + })().catch(reason => { + console.error(tr("Could not load icon %o. Reason: %s"), id, reason); + icon_load_image.removeClass("icon_loading").addClass("icon client-warning").attr("tag", "Could not load icon " + id); }); } - return tag; + return icon_container; } } class Avatar { - clientUid: string; - avatarId: string; - base64?: string; - url?: string; - blob?: Blob; + client_avatar_id: string; /* the base64 uid thing from a-m */ + avatar_id: string; /* client_flag_avatar */ + url: string; } class AvatarManager { handle: FileManager; - private loading_avatars: {promise: Promise, name: string}[] = []; - private loaded_urls: string[] = []; + + private cache: CacheManager; + private _cached_avatars: {[response_avatar_id:number]:Avatar} = {}; + private _loading_promises: {[response_avatar_id:number]:Promise} = {}; constructor(handle: FileManager) { this.handle = handle; + + this.cache = new CacheManager("avatars"); } - downloadAvatar(client: ClientEntry) : Promise { - console.log(tr("Downloading avatar %s"), client.avatarId()); - return this.handle.requestFileDownload("", "/avatar_" + client.avatarId()); + private async _response_url(response: Response) : Promise { + if(!response.headers.has('X-media-bytes')) + throw "missing media bytes"; + + const type = image_type(response.headers.get('X-media-bytes')); + const media = media_image_type(type); + + const blob = await response.blob(); + return URL.createObjectURL(blob.slice(0, blob.size, "image/" + media)); } - resolveCached?(client: ClientEntry) : Avatar { - let avatar = localStorage.getItem("avatar_" + client.properties.client_unique_identifier); + async resolved_cached?(client_avatar_id: string, avatar_id?: string) : Promise { + let avatar: Avatar = this._cached_avatars[avatar_id]; if(avatar) { - let i = JSON.parse(avatar) as Avatar; - //TODO timestamp? - - if(i.avatarId != client.properties.client_flag_avatar) return undefined; - - if(i.base64) { - if(i.base64.length > 0) - return i; - else i.base64 = undefined; - } - if(i.url) { - for(let url of this.loaded_urls) - if(url == i.url) return i; - } + if(typeof(avatar_id) !== "string" || avatar.avatar_id == avatar_id) + return avatar; + this._cached_avatars[avatar_id] = (avatar = undefined); } - return undefined; + + if(!this.cache.setupped()) + await this.cache.setup(); + + const response = await this.cache.resolve_cached('avatar_' + client_avatar_id); //TODO age! + if(!response) + return undefined; + + let response_avatar_id = response.headers.has("X-avatar-id") ? response.headers.get("X-avatar-id") : undefined; + if(typeof(avatar_id) === "string" && response_avatar_id != avatar_id) + return undefined; + + return this._cached_avatars[client_avatar_id] = { + client_avatar_id: client_avatar_id, + avatar_id: avatar_id || response_avatar_id, + url: await this._response_url(response) + }; } - private load_finished(name: string) { - for(let entry of this.loading_avatars) - if(entry.name == name) - this.loading_avatars.remove(entry); + create_avatar_download(client_avatar_id: string) : Promise { + console.log(tr("Downloading avatar %s"), client_avatar_id); + return this.handle.download_file("", "/avatar_" + client_avatar_id); } - loadAvatar(client: ClientEntry) : Promise { - let name = client.avatarId(); - for(let promise of this.loading_avatars) - if(promise.name == name) return promise.promise; - let promise = new Promise((resolve, reject) => { - let avatar = this.resolveCached(client); - if(avatar){ - this.load_finished(name); - resolve(avatar); - return; - } + private async _load_avatar(client_avatar_id: string, avatar_id: string) { + let download_key: transfer.DownloadKey; + try { + download_key = await this.create_avatar_download(client_avatar_id); + } catch(error) { + console.error(tr("Could not request download for avatar %s: %o"), client_avatar_id, error); + throw "Failed to request icon"; + } - this.downloadAvatar(client).then(ft => { - let array = new Uint8Array(0); - ft.on_fail = reason => { - this.load_finished(name); - console.error(tr("Could not download avatar %o -> %s"), client.properties.client_flag_avatar, reason); - chat.serverChat().appendError(tr("Fail to download avatar for {0}. ({1})"), client.clientNickName(), JSON.stringify(reason)); - reject(reason); - }; - ft.on_start = () => {}; - ft.on_data = (data: Uint8Array) => { - array = concatenate(Uint8Array, array, data); - }; - ft.on_complete = () => { - let avatar = new Avatar(); - if(array.length >= 1024 * 1024) { - let blob_image = new Blob([array]); - avatar.url = URL.createObjectURL(blob_image); - avatar.blob = blob_image; - this.loaded_urls.push(avatar.url); - } else { - avatar.base64 = btoa(String.fromCharCode.apply(null, array)); - } - avatar.clientUid = client.clientUid(); - avatar.avatarId = client.properties.client_flag_avatar; + const downloader = new RequestFileDownload(download_key); + let response: Response; + try { + response = await downloader.request_file(); + } catch(error) { + console.error(tr("Could not download avatar %s: %o"), client_avatar_id, error); + throw "failed to download avatar"; + } - localStorage.setItem("avatar_" + client.properties.client_unique_identifier, JSON.stringify(avatar)); - this.load_finished(name); - resolve(avatar); - }; + const type = image_type(response.headers.get('X-media-bytes')); + const media = media_image_type(type); - ft.startTransfer(); - }).catch(reason => { - this.load_finished(name); - console.error(tr("Error while downloading avatar! (%s)"), JSON.stringify(reason)); - chat.serverChat().appendError(tr("Failed to request avatar download for {0}. ({1})"), client.clientNickName(), JSON.stringify(reason)); - reject(reason); - }); + await this.cache.put_cache('avatar_' + client_avatar_id, response.clone(), "image/" + media, { + "X-avatar-id": avatar_id }); + const url = await this._response_url(response.clone()); - this.loading_avatars.push({promise: promise, name: name}); - return promise; + this._loading_promises[client_avatar_id] = undefined; + return this._cached_avatars[client_avatar_id] = { + client_avatar_id: client_avatar_id, + avatar_id: avatar_id, + url: url + }; } - generateTag(client: ClientEntry) { - let tag = $.spawn("div"); + loadAvatar(client_avatar_id: string, avatar_id: string) : Promise { + return this._loading_promises[client_avatar_id] || (this._loading_promises[client_avatar_id] = this._load_avatar(client_avatar_id, avatar_id)); + } - let img = $.spawn("img"); - img.attr("alt", ""); + generateTag(client: ClientEntry) : JQuery { + const client_avatar_id = client.avatarId(); + const avatar_id = client.properties.client_flag_avatar; - let avatar = this.resolveCached(client); - if(avatar) { - if(avatar.url) - img.attr("src", avatar.url); - else { - const type = image_type(avatar.base64); - const media = media_image_type(type); - console.debug(tr("avatar has an image type of %o (media: %o)"), type, media); - img.attr("src", "data:image/" + media + ";base64," + avatar.base64); - } - tag.append(img); + let avatar_container = $.spawn("div"); + let avatar_image = $.spawn("img").attr("alt", tr("Client avatar")); + + let cached_avatar: Avatar = this._cached_avatars[client_avatar_id]; + if(cached_avatar && cached_avatar.avatar_id == avatar_id) { + avatar_image.attr("src", cached_avatar.url); + avatar_container.append(avatar_image); } else { - let loader = $.spawn("img"); - loader.attr("src", "img/loading_image.svg").css("width", "75%"); - tag.append(loader); + let loader_image = $.spawn("img"); + loader_image.attr("src", "img/loading_image.svg").css("width", "75%"); + avatar_container.append(loader_image); - this.loadAvatar(client).then(avatar => { - if(avatar.url) - img.attr("src", avatar.url); - else { - const type = image_type(avatar.base64); - const media = media_image_type(type); - console.debug(tr("Avatar has an image type of %o (media: %o)"), type, media); - img.attr("src", "data:image/" + media + ";base64," + avatar.base64); + (async () => { + let avatar: Avatar; + try { + avatar = await this.resolved_cached(client_avatar_id, avatar_id); + } catch(error) { + console.error(error); } - console.debug("Avatar " + client.clientNickName() + " loaded :)"); - img.css("opacity", 0); - tag.append(img); - loader.animate({opacity: 0}, 50, function () { + if(!avatar) + avatar = await this.loadAvatar(client_avatar_id, avatar_id) + + avatar_image.attr("src", avatar.url); + avatar_image.css("opacity", 0); + avatar_container.append(avatar_image); + loader_image.animate({opacity: 0}, 50, function () { $(this).detach(); - img.animate({opacity: 1}, 150); + avatar_image.animate({opacity: 1}, 150); }); - }).catch(reason => { + })().catch(reason => { console.error(tr("Could not load avatar for %s. Reason: %s"), client.clientNickName(), reason); //TODO Broken image - loader.addClass("icon client-warning").attr("tag", tr("Could not load avatar ") + client.clientNickName()); - }); + loader_image.addClass("icon client-warning").attr("tag", tr("Could not load avatar ") + client.clientNickName()); + }) } - return tag; + return avatar_container; } } \ No newline at end of file diff --git a/shared/js/permission/PermissionManager.ts b/shared/js/permission/PermissionManager.ts index 61b4a1fd..5158ccf0 100644 --- a/shared/js/permission/PermissionManager.ts +++ b/shared/js/permission/PermissionManager.ts @@ -681,7 +681,7 @@ class PermissionManager extends connection.AbstractCommandHandler { let client = parseInt(json[0]["cldbid"]); let permissions = PermissionManager.parse_permission_bulk(json, this); for(let req of this.requests_client_permissions.slice(0)) { - if(req.client_id == client) { + if(req.client_avatar_id == client) { this.requests_client_permissions.remove(req); req.promise.resolved(permissions); } @@ -690,7 +690,7 @@ class PermissionManager extends connection.AbstractCommandHandler { requestClientPermissions(client_id: number) : Promise { for(let request of this.requests_client_permissions) - if(request.client_id == client_id && request.promise.time() + 1000 > Date.now()) + if(request.client_avatar_id == client_id && request.promise.time() + 1000 > Date.now()) return request.promise; let request: TeaPermissionRequest = {} as any; @@ -710,7 +710,7 @@ class PermissionManager extends connection.AbstractCommandHandler { requestClientChannelPermissions(client_id: number, channel_id: number) : Promise { for(let request of this.requests_client_channel_permissions) - if(request.client_id == client_id && request.channel_id == channel_id && request.promise.time() + 1000 > Date.now()) + if(request.client_avatar_id == client_id && request.channel_id == channel_id && request.promise.time() + 1000 > Date.now()) return request.promise; let request: TeaPermissionRequest = {} as any; diff --git a/shared/js/ui/htmltags.ts b/shared/js/ui/htmltags.ts index fa65546b..4cd11d5e 100644 --- a/shared/js/ui/htmltags.ts +++ b/shared/js/ui/htmltags.ts @@ -30,8 +30,8 @@ namespace htmltags { /* build the opening tag:
*/ result = result + "
Date.now()) + if (request.client_avatar_id == client_id && request.promise.time() + 1000 > Date.now()) return request.promise; let request = {}; request.client_id = client_id; @@ -7692,7 +7692,7 @@ class PermissionManager extends connection.AbstractCommandHandler { } requestClientChannelPermissions(client_id, channel_id) { for (let request of this.requests_client_channel_permissions) - if (request.client_id == client_id && request.channel_id == channel_id && request.promise.time() + 1000 > Date.now()) + if (request.client_avatar_id == client_id && request.channel_id == channel_id && request.promise.time() + 1000 > Date.now()) return request.promise; let request = {}; request.client_id = client_id; @@ -10761,7 +10761,7 @@ class IconManager { return this.handle.requestFileList("/icons"); } downloadIcon(id) { - return this.handle.requestFileDownload("", "/icon_" + id); + return this.handle.download_file("", "/icon_" + id); } resolveCached(id) { let icon = localStorage.getItem("icon_" + id); @@ -10811,7 +10811,7 @@ class IconManager { this.load_finished(id); resolve(icon); }; - ft.startTransfer(); + ft.start(); }).catch(reason => { console.error(_translations.DtVBEPYe || (_translations.DtVBEPYe = tr("Error while downloading icon! (%s)")), tr(JSON.stringify(reason))); chat.serverChat().appendError(_translations.z_jAHQFu || (_translations.z_jAHQFu = tr("Failed to request download for icon {0}. ({1})")), id, tr(JSON.stringify(reason))); @@ -10874,7 +10874,7 @@ class AvatarManager { } downloadAvatar(client) { console.log(_translations.FXXPuv5A || (_translations.FXXPuv5A = tr("Downloading avatar %s")), client.avatarId()); - return this.handle.requestFileDownload("", "/avatar_" + client.avatarId()); + return this.handle.download_file("", "/avatar_" + client.avatarId()); } resolveCached(client) { let avatar = localStorage.getItem("avatar_" + client.properties.client_unique_identifier); @@ -10943,7 +10943,7 @@ class AvatarManager { this.load_finished(name); resolve(avatar); }; - ft.startTransfer(); + ft.start(); }).catch(reason => { this.load_finished(name); console.error(_translations.ynLLTaB0 || (_translations.ynLLTaB0 = tr("Error while downloading avatar! (%s)")), JSON.stringify(reason)); @@ -16401,8 +16401,8 @@ var htmltags; let result = ""; /* build the opening tag:
*/ result = result + "
Date: Fri, 22 Mar 2019 23:33:42 +0100 Subject: [PATCH 02/13] Disallowing zooming --- shared/css/static/general.scss | 5 +++++ shared/html/index.php | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/shared/css/static/general.scss b/shared/css/static/general.scss index c0d474b8..a7d704e9 100644 --- a/shared/css/static/general.scss +++ b/shared/css/static/general.scss @@ -43,6 +43,11 @@ } } +@viewport { + width: device-width; + user-zoom:fixed; +} + .select_info { font-family: Arial; font-size: 12px; diff --git a/shared/html/index.php b/shared/html/index.php index b382804f..93750af9 100644 --- a/shared/html/index.php +++ b/shared/html/index.php @@ -35,9 +35,11 @@ - + + + Date: Mon, 25 Mar 2019 20:03:51 +0100 Subject: [PATCH 03/13] Added russian translation by Vafin --- shared/css/static/modal-avatar.scss | 0 shared/css/static/modal-icons.scss | 0 shared/html/manifest.json | 16 + shared/i18n/info.json | 4 + shared/i18n/ru_translate_vafin.translation | 8819 ++++++++++++++++++++ shared/js/ui/modal/ModalAvatarList.ts | 0 shared/js/ui/modal/ModalIconSelect.ts | 0 7 files changed, 8839 insertions(+) create mode 100644 shared/css/static/modal-avatar.scss create mode 100644 shared/css/static/modal-icons.scss create mode 100644 shared/html/manifest.json create mode 100644 shared/i18n/ru_translate_vafin.translation create mode 100644 shared/js/ui/modal/ModalAvatarList.ts create mode 100644 shared/js/ui/modal/ModalIconSelect.ts diff --git a/shared/css/static/modal-avatar.scss b/shared/css/static/modal-avatar.scss new file mode 100644 index 00000000..e69de29b diff --git a/shared/css/static/modal-icons.scss b/shared/css/static/modal-icons.scss new file mode 100644 index 00000000..e69de29b diff --git a/shared/html/manifest.json b/shared/html/manifest.json new file mode 100644 index 00000000..fc0b7a5e --- /dev/null +++ b/shared/html/manifest.json @@ -0,0 +1,16 @@ +{ + "short_name": "TeaWeb", + "name": "TeaSpeak Web", + "icons": [ + { + "src": "img/favicon/teacup.png", + "type": "image/png", + "sizes": "256x256" + } + ], + "start_url": "?", + "background_color": "#18BC9C", + "display": "standalone", + "scope": "/", + "theme_color": "#18BC9C" +} \ No newline at end of file diff --git a/shared/i18n/info.json b/shared/i18n/info.json index 4cd17cf2..7cbb4a29 100644 --- a/shared/i18n/info.json +++ b/shared/i18n/info.json @@ -15,6 +15,10 @@ { "key": "fr_gt", "path": "fr_google_translate.translation" + }, + { + "key": "ru", + "path": "ru_translate_vafin.translation" } ], "name": "Default TeaSpeak repository", diff --git a/shared/i18n/ru_translate_vafin.translation b/shared/i18n/ru_translate_vafin.translation new file mode 100644 index 00000000..23f85423 --- /dev/null +++ b/shared/i18n/ru_translate_vafin.translation @@ -0,0 +1,8819 @@ +{ + "info": { + "name": "Russion translation by Vafin, baste on google translate", + "contributors": [ + { + "name": "Google Translate, via script by Markus Hadenfeldt", + "email": "gtr.i18n.client@teaspeak.de" + }, + { + "name": "Vafin", + "email": "" + } + ] + }, + "translations": [ + { + "translated": "Не удалось инициализировать аудиоконтроллер!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to initialize audio controller!" + } + }, + { + "translated": "Звуки инициализированы", + "flags": [ + "google-translate" + ], + "key": { + "message": "Sounds initialitzed" + } + }, + { + "translated": "[AudioController] Получил пустой или неопределенный буфер! Сбросив его", + "flags": [ + "google-translate" + ], + "key": { + "message": "[AudioController] Got empty or undefined buffer! Dropping it" + } + }, + { + "translated": "[AudioController] Не удалось воспроизвести аудио. Глобальный аудио контекст еще не инициализирован!", + "flags": [ + "google-translate" + ], + "key": { + "message": "[AudioController] Failed to replay audio. Global audio context not initialized yet!" + } + }, + { + "translated": "[AudioController] Частота дискретизации источника не равна частоте дискретизации воспроизведения! (% o |% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "[AudioController] Source sample rate isn't equal to playback sample rate! (%o | %o)" + } + }, + { + "translated": "[Аудио] Начало нового воспроизведения", + "flags": [ + "google-translate" + ], + "key": { + "message": "[Audio] Starting new playback" + } + }, + { + "translated": "[Аудио] Пребуферинг успешно (воспроизведение сейчас)", + "flags": [ + "google-translate" + ], + "key": { + "message": "[Audio] Prebuffering succeeded (Replaying now)" + } + }, + { + "translated": "[Аудио] Буферизация успешно (воспроизведение сейчас)", + "flags": [ + "google-translate" + ], + "key": { + "message": "[Audio] Buffering succeeded (Replaying now)" + } + }, + { + "translated": "Сброс буфера, потому что очередь воспроизведения увеличивается до очень", + "flags": [ + "google-translate" + ], + "key": { + "message": "Dropping buffer because playing queue grows to much" + } + }, + { + "translated": "[Аудио] Обнаружен переполнение буфера!", + "flags": [ + "google-translate" + ], + "key": { + "message": "[Audio] Detected a buffer underflow!" + } + }, + { + "translated": "[Аудио] Превышено время ожидания буферизации. Промывка и остановка воспроизведения", + "flags": [ + "google-translate" + ], + "key": { + "message": "[Audio] Buffering exceeded timeout. Flushing and stopping replay" + } + }, + { + "translated": "Json не содержит% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Json does not contains %s" + } + }, + { + "translated": "Валидатор приводит к ложному для% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Validator results in false for %s" + } + }, + { + "translated": "Недопустимый тип объекта% s для записи% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Invalid object type %s for entry %s" + } + }, + { + "translated": "лет", + "flags": [ + "google-translate" + ], + "key": { + "message": "years" + } + }, + { + "translated": "дней", + "flags": [ + "google-translate" + ], + "key": { + "message": "days" + } + }, + { + "translated": "часов", + "flags": [ + "google-translate" + ], + "key": { + "message": "hours" + } + }, + { + "translated": "минут", + "flags": [ + "google-translate" + ], + "key": { + "message": "minutes" + } + }, + { + "translated": "секунд", + "flags": [ + "google-translate" + ], + "key": { + "message": "seconds" + } + }, + { + "translated": "сейчас", + "flags": [ + "google-translate" + ], + "key": { + "message": "now" + } + }, + { + "translated": "VAD изменился!", + "flags": [ + "google-translate" + ], + "key": { + "message": "VAD changed!" + } + }, + { + "translated": "Обнаружение ключа VAD изменено.
Пожалуйста, сбросьте свой ключ PPT!", + "flags": [ + "google-translate" + ], + "key": { + "message": "VAD key detection changed.
Please reset your PPT key!" + } + }, + { + "translated": "Неверный обработчик VAD (детектор голосовой активации)! (% О)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Invalid VAD (Voice activation detector) handler! (%o)" + } + }, + { + "translated": "[VoiceRecorder] Начните запись! (Устройство:% o | Группа:% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "[VoiceRecorder] Start recording! (Device: %o | Group: %o)" + } + }, + { + "translated": "Не удалось разрешить микрофон!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not resolve microphone!" + } + }, + { + "translated": "Не удалось разрешить микрофон!
Сообщение:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not resolve microphone!
Message: " + } + }, + { + "translated": "Не удалось достать микрофон!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not get microphone!" + } + }, + { + "translated": "Прекратите запись!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Stop recording!" + } + }, + { + "translated": "[VoiceRecorder] У вас есть микрофонный поток, но у него нет аудио контекста. В ожидании его инициализации", + "flags": [ + "google-translate" + ], + "key": { + "message": "[VoiceRecorder] Got microphone stream, but havn't a audio context. Waiting until its initialized" + } + }, + { + "translated": "Отпустите снова! (% О)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Release again! (%o)" + } + }, + { + "translated": "Отключение поддержки кодеков для", + "flags": [ + "google-translate" + ], + "key": { + "message": "Disabling codec support for " + } + }, + { + "translated": "Не удалось загрузить драйвер кодека", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not load codec driver" + } + }, + { + "translated": "Не удалось загрузить или инициализировать кодек", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not load or initialize codec " + } + }, + { + "translated": "Не удалось инициализировать кодек opus. Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to initialize the opus codec. Error: %o" + } + }, + { + "translated": "Не удалось инициализировать уже отключенный кодек. Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to initialize already disabled codec. Error: %o" + } + }, + { + "translated": "неподдерживаемый кодек!", + "flags": [ + "google-translate" + ], + "key": { + "message": "unsupported codec!" + } + }, + { + "translated": "Не удалось инициализировать кодек! Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not initialize codec!\nError: %o" + } + }, + { + "translated": "Не удалось инициализировать кодек!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not initialize codec!" + } + }, + { + "translated": "Speex Narrowband", + "flags": [ + "google-translate" + ], + "key": { + "message": "Speex Narrowband" + } + }, + { + "translated": "Speex Wideband", + "flags": [ + "google-translate" + ], + "key": { + "message": "Speex Wideband" + } + }, + { + "translated": "Speex Ultra Wideband", + "flags": [ + "google-translate" + ], + "key": { + "message": "Speex Ultra Wideband" + } + }, + { + "translated": "КЕЛЬТ моно", + "flags": [ + "google-translate" + ], + "key": { + "message": "CELT Mono" + } + }, + { + "translated": "Опус Голос", + "flags": [ + "google-translate" + ], + "key": { + "message": "Opus Voice" + } + }, + { + "translated": "Опус Музыка", + "flags": [ + "google-translate" + ], + "key": { + "message": "Opus Music" + } + }, + { + "translated": "Инициализация голосового обработчика после инициализации AudioController!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Initializing voice handler after AudioController has been initialized!" + } + }, + { + "translated": "Настройка родного голосового потока!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Setting up native voice stream!" + } + }, + { + "translated": "Родной кодек не поддерживается!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Native codec isnt supported!" + } + }, + { + "translated": "Не удалось передать звук (не подключен)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not transfer audio (not connected)" + } + }, + { + "translated": "Аудио плеер еще не инициализирован. В ожидании жеста.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Audio player isn't initialized yet. Waiting for gesture." + } + }, + { + "translated": "Добавление потока (% o)!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Adding stream (%o)!" + } + }, + { + "translated": "Не удалось создать ледовое предложение!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not create ice offer!" + } + }, + { + "translated": "Установите удаленный sdp! (% О)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Set remote sdp! (%o)" + } + }, + { + "translated": "Не удалось применить удаленное описание:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to apply remote description: %o" + } + }, + { + "translated": "Не удалось добавить удаленный кэшированный кандидат со льдом% s:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to add remote cached ice candidate %s: %o" + } + }, + { + "translated": "Добавьте удаленный лед! (% s |% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Add remote ice! (%s | %o)" + } + }, + { + "translated": "Не удалось добавить удаленного кандидата на лед% s:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to add remote ice candidate %s: %o" + } + }, + { + "translated": "Кеш удаленного льда! (% s |% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Cache remote ice! (%s | %o)" + } + }, + { + "translated": "Не удалось настроить голосовой мост ({}). Разрешить переподключение: {}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to setup voice bridge ({}). Allow reconnect: {}" + } + }, + { + "translated": "Не удалось настроить голосовой мост (% s). Разрешить переподключение:% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to setup voice bridge (%s). Allow reconnect: %s" + } + }, + { + "translated": "Получил ледяной кандидат! Событие:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got ice candidate! Event:" + } + }, + { + "translated": "Предложение создано и принято", + "flags": [ + "google-translate" + ], + "key": { + "message": "Offer created and accepted" + } + }, + { + "translated": "Не удалось применить локальное описание:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to apply local description: %o" + } + }, + { + "translated": "Отправить предложение:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Send offer: %o" + } + }, + { + "translated": "Получил новый канал данных! (% S)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got new data channel! (%s)" + } + }, + { + "translated": "Имея голос от неизвестного клиента? (ClientID:% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Having voice from unknown client? (ClientID: %o)" + } + }, + { + "translated": "Не удалось воспроизвести кодек% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not playback codec %o" + } + }, + { + "translated": "Не удалось воспроизвести аудио клиента (% o) (% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not playback client's (%o) audio (%o)" + } + }, + { + "translated": "Местный голос закончился", + "flags": [ + "google-translate" + ], + "key": { + "message": "Local voice ended" + } + }, + { + "translated": "Местный голос начался", + "flags": [ + "google-translate" + ], + "key": { + "message": "Local voice started" + } + }, + { + "translated": "Канал по умолчанию", + "flags": [ + "google-translate" + ], + "key": { + "message": "Default channel" + } + }, + { + "translated": "Канал защищен паролем", + "flags": [ + "google-translate" + ], + "key": { + "message": "The channel is password protected" + } + }, + { + "translated": "Качество музыки", + "flags": [ + "google-translate" + ], + "key": { + "message": "Music quality" + } + }, + { + "translated": "Канал модерируется", + "flags": [ + "google-translate" + ], + "key": { + "message": "Channel is moderated" + } + }, + { + "translated": "Значок канала", + "flags": [ + "google-translate" + ], + "key": { + "message": "Channel icon" + } + }, + { + "translated": "Показать информацию о канале", + "flags": [ + "google-translate" + ], + "key": { + "message": "Show channel info" + } + }, + { + "translated": "Переключиться на канал", + "flags": [ + "google-translate" + ], + "key": { + "message": "Switch to channel" + } + }, + { + "translated": "Подписаться на канал", + "flags": [ + "google-translate" + ], + "key": { + "message": "Subscribe to channel" + } + }, + { + "translated": "Отписаться от канала", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unsubscribe from channel" + } + }, + { + "translated": "Использовать унаследованный режим подписки", + "flags": [ + "google-translate" + ], + "key": { + "message": "Use inherited subscribe mode" + } + }, + { + "translated": "Изменить канал", + "flags": [ + "google-translate" + ], + "key": { + "message": "Edit channel" + } + }, + { + "translated": "Изменены свойства канала для канала% s:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Changed channel properties of channel %s: %o" + } + }, + { + "translated": "Удалить канал", + "flags": [ + "google-translate" + ], + "key": { + "message": "Delete channel" + } + }, + { + "translated": "Создать музыкального бота", + "flags": [ + "google-translate" + ], + "key": { + "message": "Create music bot" + } + }, + { + "translated": "Бот успешно создан", + "flags": [ + "google-translate" + ], + "key": { + "message": "Bot successfully created" + } + }, + { + "translated": "Бот был успешно создан.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Bot has been successfully created." + } + }, + { + "translated": "Не удалось создать бота", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to create bot" + } + }, + { + "translated": "Не удалось создать музыкального бота:
{0}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to create the music bot:
{0}" + } + }, + { + "translated": "Создать подканал", + "flags": [ + "google-translate" + ], + "key": { + "message": "Create sub channel" + } + }, + { + "translated": "Создать канал", + "flags": [ + "google-translate" + ], + "key": { + "message": "Create channel" + } + }, + { + "translated": "Параметры канала: "% o"", + "flags": [ + "google-translate" + ], + "key": { + "message": "Channel options: '%o'" + } + }, + { + "translated": "Получил отформатированное название канала:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got formated channel name: %o" + } + }, + { + "translated": "Повторная прокладка заняла слишком много повторов!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Repeating spacer took too much repeats!" + } + }, + { + "translated": "Выровнять:% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Align: %s" + } + }, + { + "translated": "Обновить свойства (% i)% s (% i)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Update properties (%i) of %s (%i)" + } + }, + { + "translated": "Обновление свойства% s = '% s' ->% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Updating property %s = '%s' -> %o" + } + }, + { + "translated": "Пароль канала", + "flags": [ + "google-translate" + ], + "key": { + "message": "Channel password" + } + }, + { + "translated": "Пароль канала:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Channel password:" + } + }, + { + "translated": "Изменить громкость", + "flags": [ + "google-translate" + ], + "key": { + "message": "Change volume" + } + }, + { + "translated": "Сброс", + "flags": [ + "google-translate" + ], + "key": { + "message": "Reset" + } + }, + { + "translated": "отменить", + "flags": [ + "google-translate" + ], + "key": { + "message": "Cancel" + } + }, + { + "translated": "ОК", + "flags": [ + "google-translate" + ], + "key": { + "message": "OK" + } + }, + { + "translated": "Применить", + "flags": [ + "google-translate" + ], + "key": { + "message": "Apply" + } + }, + { + "translated": "Группы серверов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server Groups" + } + }, + { + "translated": "Не удалось разрешить целевую группу!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not resolve target group!" + } + }, + { + "translated": "близко", + "flags": [ + "google-translate" + ], + "key": { + "message": "Close" + } + }, + { + "translated": "Начиная движение мыши", + "flags": [ + "google-translate" + ], + "key": { + "message": "Starting mouse move" + } + }, + { + "translated": "Установить группу серверов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Set server group" + } + }, + { + "translated": "Установить группу каналов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Set channel group" + } + }, + { + "translated": "права доступа", + "flags": [ + "google-translate" + ], + "key": { + "message": "Permissions" + } + }, + { + "translated": "Показать информацию о клиенте", + "flags": [ + "google-translate" + ], + "key": { + "message": "Show client info" + } + }, + { + "translated": "Открыть текстовый чат", + "flags": [ + "google-translate" + ], + "key": { + "message": "Open text chat" + } + }, + { + "translated": "Тыкать клиента", + "flags": [ + "google-translate" + ], + "key": { + "message": "Poke client" + } + }, + { + "translated": "Тыкать сообщение:
", + "flags": [ + "google-translate" + ], + "key": { + "message": "Poke message:
" + } + }, + { + "translated": "Изменить описание", + "flags": [ + "google-translate" + ], + "key": { + "message": "Change description" + } + }, + { + "translated": "Изменить описание клиента", + "flags": [ + "google-translate" + ], + "key": { + "message": "Change client description" + } + }, + { + "translated": "Новое описание:
", + "flags": [ + "google-translate" + ], + "key": { + "message": "New description:
" + } + }, + { + "translated": "Переместить клиента на ваш канал", + "flags": [ + "google-translate" + ], + "key": { + "message": "Move client to your channel" + } + }, + { + "translated": "Кик клиент с канала", + "flags": [ + "google-translate" + ], + "key": { + "message": "Kick client from channel" + } + }, + { + "translated": "Причина удара:
", + "flags": [ + "google-translate" + ], + "key": { + "message": "Kick reason:
" + } + }, + { + "translated": "Кик клиент с сервера", + "flags": [ + "google-translate" + ], + "key": { + "message": "Kick client fom server" + } + }, + { + "translated": "Kick клиент с сервера", + "flags": [ + "google-translate" + ], + "key": { + "message": "Kick client from server" + } + }, + { + "translated": "Запрет клиента", + "flags": [ + "google-translate" + ], + "key": { + "message": "Ban client" + } + }, + { + "translated": "Изменить громкость", + "flags": [ + "google-translate" + ], + "key": { + "message": "Change Volume" + } + }, + { + "translated": "Не удалось уведомить участника чата (% o) о том, что чат был закрыт. Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to notify chat participant (%o) that the chat has been closed. Error: %o" + } + }, + { + "translated": "Сменить имя", + "flags": [ + "google-translate" + ], + "key": { + "message": "Change name" + } + }, + { + "translated": "Изменить собственное описание", + "flags": [ + "google-translate" + ], + "key": { + "message": "Change own description" + } + }, + { + "translated": "Изменение собственного описания на% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Changing own description to %s" + } + }, + { + "translated": "Псевдоним успешно изменен", + "flags": [ + "google-translate" + ], + "key": { + "message": "Nickname successfully changed" + } + }, + { + "translated": "Не удалось изменить ник ({})", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not change nickname ({})" + } + }, + { + "translated": "Показать информацию о боте", + "flags": [ + "google-translate" + ], + "key": { + "message": "Show bot info" + } + }, + { + "translated": "Изменить имя бота", + "flags": [ + "google-translate" + ], + "key": { + "message": "Change bot name" + } + }, + { + "translated": "Изменить никнейм ботов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Change music bots nickname" + } + }, + { + "translated": "Новый никнейм:
", + "flags": [ + "google-translate" + ], + "key": { + "message": "New nickname:
" + } + }, + { + "translated": "Изменить описание бота", + "flags": [ + "google-translate" + ], + "key": { + "message": "Change bot description" + } + }, + { + "translated": "Изменить описание музыкальных ботов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Change music bots description" + } + }, + { + "translated": "Открыть плейлист бота", + "flags": [ + "google-translate" + ], + "key": { + "message": "Open bot's playlist" + } + }, + { + "translated": "Неверные разрешения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Invalid permissions" + } + }, + { + "translated": "Вы не должны видеть список воспроизведения ботов.", + "flags": [ + "google-translate" + ], + "key": { + "message": "You dont have to see the bots playlist." + } + }, + { + "translated": "Не удалось запросить плейлист.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to query playlist." + } + }, + { + "translated": "Не удалось запросить информацию о плейлисте.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to query playlist info." + } + }, + { + "translated": "Быстрое воспроизведение URL", + "flags": [ + "google-translate" + ], + "key": { + "message": "Quick url replay" + } + }, + { + "translated": "Пожалуйста, введите URL", + "flags": [ + "google-translate" + ], + "key": { + "message": "Please enter the URL" + } + }, + { + "translated": "URL:", + "flags": [ + "google-translate" + ], + "key": { + "message": "URL:" + } + }, + { + "translated": "Не удалось воспроизвести URL", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to replay url" + } + }, + { + "translated": "Удаление клиента% o из канала с причиной% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Kicking client %o from channel with reason %o" + } + }, + { + "translated": "Изменить локальный том", + "flags": [ + "google-translate" + ], + "key": { + "message": "Change local volume" + } + }, + { + "translated": "Изменить удаленную громкость", + "flags": [ + "google-translate" + ], + "key": { + "message": "Change remote volume" + } + }, + { + "translated": "Удалить бота", + "flags": [ + "google-translate" + ], + "key": { + "message": "Delete bot" + } + }, + { + "translated": "Вы действительно хотите удалить {0}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Do you really want to delete {0}" + } + }, + { + "translated": "Уверены ли вы?", + "flags": [ + "google-translate" + ], + "key": { + "message": "Are you sure?" + } + }, + { + "translated": "ОК", + "flags": [ + "google-translate" + ], + "key": { + "message": "Ok" + } + }, + { + "translated": "Не удалось разрешить разрешение канала для имени% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to resolve channel permission for name %o" + } + }, + { + "translated": "Обновлены разрешения% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Updated permissions %o" + } + }, + { + "translated": "Получил разрешения:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got permissions: %o" + } + }, + { + "translated": "Разрешение сработало! % о", + "flags": [ + "google-translate" + ], + "key": { + "message": "Permission triggered! %o" + } + }, + { + "translated": "Неверный ход канала (разные родители! (% O |% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Invalid channel move (different parents! (%o|%o)" + } + }, + { + "translated": "Мультиселектный канал", + "flags": [ + "google-translate" + ], + "key": { + "message": "Multiselect channel" + } + }, + { + "translated": "Мультиселект клиент", + "flags": [ + "google-translate" + ], + "key": { + "message": "Multiselect client" + } + }, + { + "translated": "Только музыка:% o | Контейнерная музыка:% o | Контейнер локальный:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Music only: %o | Container music: %o | Container local: %o" + } + }, + { + "translated": "Тыкать клиентов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Poke clients" + } + }, + { + "translated": "Переместить клиентов на ваш канал", + "flags": [ + "google-translate" + ], + "key": { + "message": "Move clients to your channel" + } + }, + { + "translated": "Кик клиенты с канала", + "flags": [ + "google-translate" + ], + "key": { + "message": "Kick clients from channel" + } + }, + { + "translated": "Кик клиенты с сервера", + "flags": [ + "google-translate" + ], + "key": { + "message": "Kick clients fom server" + } + }, + { + "translated": "Кик клиенты с сервера", + "flags": [ + "google-translate" + ], + "key": { + "message": "Kick clients from server" + } + }, + { + "translated": "Бан клиентов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Ban clients" + } + }, + { + "translated": "Удалить ботов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Delete bots" + } + }, + { + "translated": "Вы действительно хотите удалить", + "flags": [ + "google-translate" + ], + "key": { + "message": "Do you really want to delete " + } + }, + { + "translated": "Создание нового канала. Свойства:% o Разрешения:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Creating a new channel.\nProperties: %o\nPermissions: %o" + } + }, + { + "translated": "Не удалось разрешить канал после создания. Не удалось применить разрешения!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to resolve channel after creation. Could not apply permissions!" + } + }, + { + "translated": "Канал {} успешно создан!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Channel {} successfully created!" + } + }, + { + "translated": "Не удалось подписаться на определенные каналы (% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to subscribe to specific channels (%o)" + } + }, + { + "translated": "Не удалось отписаться от всех каналов! (% О)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to unsubscribe to all channels! (%o)" + } + }, + { + "translated": "Не удалось отменить подписку на определенные каналы (% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to unsubscribe to specific channels (%o)" + } + }, + { + "translated": "Не удалось подписаться на все каналы! (% О)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to subscribe to all channels! (%o)" + } + }, + { + "translated": "Использование информационного менеджера:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Using info manager: %o" + } + }, + { + "translated": "Не удалось загрузить hostbanner:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to load hostbanner: %o" + } + }, + { + "translated": "Не удалось разобрать URL баннера:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to parse banner URL: %o" + } + }, + { + "translated": "Не удалось загрузить hostbanner, так как 곎ss-Control-Allow-Origin» недопустим!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not load hostbanner because 'Access-Control-Allow-Origin' isnt valid!" + } + }, + { + "translated": "Получил хост-баннер успешно (% o, тип:% o, URL:% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Fetsched hostbanner successfully (%o, type: %o, url: %o)" + } + }, + { + "translated": "Отзыв URL-адреса хост-банка% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Revoked hostbanner url %s" + } + }, + { + "translated": "Не удалось получить изображение hostbanner:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to fetch hostbanner image: %o" + } + }, + { + "translated": "Hostbanner был загружен", + "flags": [ + "google-translate" + ], + "key": { + "message": "Hostbanner has been loaded" + } + }, + { + "translated": "загрузка ...", + "flags": [ + "google-translate" + ], + "key": { + "message": "loading..." + } + }, + { + "translated": "Нет названия или URL", + "flags": [ + "google-translate" + ], + "key": { + "message": "No title or url" + } + }, + { + "translated": "Не удалось выполнить игру", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to execute play" + } + }, + { + "translated": "Не удалось выполнить игру.
{}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to execute play.
{}" + } + }, + { + "translated": "Не удалось выполнить паузу", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to execute pause" + } + }, + { + "translated": "Не удалось выполнить паузу.
{}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to execute pause.
{}" + } + }, + { + "translated": "Не удалось выполнить остановку", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to execute stop" + } + }, + { + "translated": "Не удалось выполнить остановку.
{}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to execute stop.
{}" + } + }, + { + "translated": "Не удалось выполнить вперед", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to execute forward" + } + }, + { + "translated": "Не удалось выполнить перемотку", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to execute rewind" + } + }, + { + "translated": "Вы не должны видеть список воспроизведения ботов.", + "flags": [ + "google-translate" + ], + "key": { + "message": "You don't have to see the bots playlist." + } + }, + { + "translated": "Родители:% o | % o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Parents: %o | %o" + } + }, + { + "translated": "Игрок:% o | %, то", + "flags": [ + "google-translate" + ], + "key": { + "message": "Player: %o | %o" + } + }, + { + "translated": "Масштаб:% f => перевод:% o | % o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Scale: %f => translate: %o | %o" + } + }, + { + "translated": "Попытался отменить регистрацию командного обработчика, который не принадлежит боссу обработчика", + "flags": [ + "google-translate" + ], + "key": { + "message": "Tried to unregister command handler which does not belong to the handler boss" + } + }, + { + "translated": "Не удалось вызвать обработчик команд. В результате вызова возникает исключение:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to invoke command handler. Invocation results in an exception: %o" + } + }, + { + "translated": "Не удалось вызвать обработчик одной команды. В результате вызова возникает исключение:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to invoke single command handler. Invocation results in an exception: %o" + } + }, + { + "translated": "Требуется проверка:% o | % я | % o =>% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Test needed required: %o | %i | %o => %o" + } + }, + { + "translated": "Получил неизвестный идентификатор разрешения (% o /% o (% o))!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got unknown permission id (%o/%o (%o))!" + } + }, + { + "translated": "Отображение разрешений", + "flags": [ + "google-translate" + ], + "key": { + "message": "Permission mapping" + } + }, + { + "translated": "группа", + "flags": [ + "google-translate" + ], + "key": { + "message": "Group " + } + }, + { + "translated": "% i <>% s ->% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "%i <> %s -> %s" + } + }, + { + "translated": "Получил% i разрешений", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got %i permissions" + } + }, + { + "translated": "Получены необходимые разрешения, но нет списка разрешений!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got needed permissions but don't have a permission list!" + } + }, + { + "translated": "Получил% d необходимых разрешений.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got %d needed permissions." + } + }, + { + "translated": "Не удалось разрешить Пермь для идентификатора% s (% o |% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not resolve perm for id %s (%o|%o)" + } + }, + { + "translated": "Получил разрешения на канал для канала% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got channel permissions for channel %o" + } + }, + { + "translated": "Отсутствует дескриптор разрешения канала для запрошенного идентификатора канала% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Missing channel permission handle for requested channel id %o" + } + }, + { + "translated": "Не удалось разрешить разрешение на выдачу% o. Создание нового.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not resolve grant permission %o. Creating a new one." + } + }, + { + "translated": "Запрошено необходимое разрешение с неверным ключом! (% О)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Requested needed permission with invalid key! (%o)" + } + }, + { + "translated": "неверный заказ!", + "flags": [ + "google-translate" + ], + "key": { + "message": "invalid order!" + } + }, + { + "translated": "Не удалось разрешить цель группы! =>% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not resolve group target! => %o" + } + }, + { + "translated": "Получил групповые разрешения для группы% o /% o, но это не зарегистрированная группа!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got group permissions for group %o/%o, but its not a registered group!" + } + }, + { + "translated": "Получил групповые разрешения для группы% o /% o, но это никогда не запрашивалось!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got group permissions for group %o/%o, but it was never requested!" + } + }, + { + "translated": "Не удалось загрузить файл перевода% s. Не удалось проанализировать или обработать JSON:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to load translation file %s. Failed to parse or process json: %o" + } + }, + { + "translated": "Не удалось обработать или проанализировать JSON!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to process or parse json!" + } + }, + { + "translated": "Не удалось загрузить файл:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to load file: " + } + }, + { + "translated": "Успешно инициализирован файл перевода из% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Successfully initialized up translation file from %s" + } + }, + { + "translated": "Не удалось загрузить файл перевода из "% s". Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to load translation file from \"%s\". Error: %o" + } + }, + { + "translated": "Успешно добавлено хранилище по умолчанию из "% s".", + "flags": [ + "google-translate" + ], + "key": { + "message": "Successfully added default repository from \"%s\"." + } + }, + { + "translated": "Не удалось добавить репозиторий по умолчанию. Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to add default repository. Error: %o" + } + }, + { + "translated": "Не удалось загрузить файл перевода для хранилища% s. Перевод:% s (% s) Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to load translation file for repository %s. Translation: %s (%s) Error: %o" + } + }, + { + "translated": "Не удалось загрузить хранилище во время итерации:% s (% s). Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to load repository while iteration: %s (%s). Error: %o" + } + }, + { + "translated": "Система перевода", + "flags": [ + "google-translate" + ], + "key": { + "message": "Translation System" + } + }, + { + "translated": "Не удалось загрузить текущий выбранный файл перевода.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to load current selected translation file." + } + }, + { + "translated": "Использование стандартных резервных переводов.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Using default fallback translations." + } + }, + { + "translated": "Не удалось бросить вызов вкладке customElements!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not defied tab customElements!" + } + }, + { + "translated": "Получена неизвестная команда во время рукопожатия (% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Received unknown command while handshaking (%o)" + } + }, + { + "translated": "Улучшить личность", + "flags": [ + "google-translate" + ], + "key": { + "message": "Improve identity" + } + }, + { + "translated": "Начните", + "flags": [ + "google-translate" + ], + "key": { + "message": "Start" + } + }, + { + "translated": "Стоп", + "flags": [ + "google-translate" + ], + "key": { + "message": "Stop" + } + }, + { + "translated": "Не удалось улучшить личность", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to improve identity" + } + }, + { + "translated": "Не удалось улучшить личность.
Ошибка:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to improve identity.
Error:" + } + }, + { + "translated": "Личность успешно улучшена", + "flags": [ + "google-translate" + ], + "key": { + "message": "Identity successfully improved" + } + }, + { + "translated": "Идентичность успешно улучшена до уровня {}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Identity successfully improved to level {}" + } + }, + { + "translated": "Импорт идентификатора", + "flags": [ + "google-translate" + ], + "key": { + "message": "Import identity" + } + }, + { + "translated": "Не удалось прочитать файл идентификации:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to read give identity file: %o" + } + }, + { + "translated": "Не удалось прочитать файл!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to read file!" + } + }, + { + "translated": "настройки", + "flags": [ + "google-translate" + ], + "key": { + "message": "Settings" + } + }, + { + "translated": "да", + "flags": [ + "google-translate" + ], + "key": { + "message": "yes" + } + }, + { + "translated": "нет", + "flags": [ + "google-translate" + ], + "key": { + "message": "no" + } + }, + { + "translated": "Введите ключ, который вы хотите", + "flags": [ + "google-translate" + ], + "key": { + "message": "Type the key you wish" + } + }, + { + "translated": "Получил ключ% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got key %o" + } + }, + { + "translated": "Нет устройства", + "flags": [ + "google-translate" + ], + "key": { + "message": "No device" + } + }, + { + "translated": "Получил устройство% s (% s):% s (% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got device %s (%s): %s (%o)" + } + }, + { + "translated": "Не могу перечислить по устройствам!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not enumerate over devices!" + } + }, + { + "translated": "Не удалось получить список устройств микрофона!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not get microphone device list!" + } + }, + { + "translated": "Выбранное микрофонное устройство: id:% o группа:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Selected microphone device: id: %o group: %o" + } + }, + { + "translated": "Не удалось получить список устройств динамиков!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not get speaker device list!" + } + }, + { + "translated": "Выбранное устройство динамика: идентификатор:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Selected speaker device: id: %o" + } + }, + { + "translated": "Не удалось сменить устройство!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to change device!" + } + }, + { + "translated": "Звук% s изменился на% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Sound %s had changed to %o" + } + }, + { + "translated": "Информация о хранилище", + "flags": [ + "google-translate" + ], + "key": { + "message": "Repository info" + } + }, + { + "translated": "Вы действительно хотите удалить этот репозиторий?", + "flags": [ + "google-translate" + ], + "key": { + "message": "Do you really want to delete this repository?" + } + }, + { + "translated": "Информация о переводе", + "flags": [ + "google-translate" + ], + "key": { + "message": "Translation info" + } + }, + { + "translated": "Введите URL хранилища:
", + "flags": [ + "google-translate" + ], + "key": { + "message": "Enter repository URL:
" + } + }, + { + "translated": "Не удалось запросить хранилище.
Убедитесь, что этот репозиторий действителен и доступен.
Ошибка:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to query repository.
Ensure that this repository is valid and reachable.
Error: " + } + }, + { + "translated": "Идентичность импортирована", + "flags": [ + "google-translate" + ], + "key": { + "message": "Identity imported" + } + }, + { + "translated": "Ваша личность была успешно импортирована!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Your identity has been successfully imported!" + } + }, + { + "translated": "Уверены ли вы", + "flags": [ + "google-translate" + ], + "key": { + "message": "Are you sure" + } + }, + { + "translated": "Вы действительно хотите импортировать новый идентификатор и переопределить старый?", + "flags": [ + "google-translate" + ], + "key": { + "message": "Do you really want to import a new identity and override the old identity?" + } + }, + { + "translated": "Имя файла", + "flags": [ + "google-translate" + ], + "key": { + "message": "File name" + } + }, + { + "translated": "Пожалуйста, введите имя файла", + "flags": [ + "google-translate" + ], + "key": { + "message": "Please enter the file name" + } + }, + { + "translated": "Не удалось экспортировать личность", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to export identity" + } + }, + { + "translated": "Не удалось экспортировать и сохранить личность.
Ошибка:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to export and save identity.
Error: " + } + }, + { + "translated": "Идентичность генерировать", + "flags": [ + "google-translate" + ], + "key": { + "message": "Identity generate" + } + }, + { + "translated": "Новая личность была успешно создана", + "flags": [ + "google-translate" + ], + "key": { + "message": "A new identity had been successfully generated" + } + }, + { + "translated": "Не удалось создать новую личность. Объект ошибки:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to generate a new identity. Error object: %o" + } + }, + { + "translated": "Не удалось сгенерировать личность", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to generate identity" + } + }, + { + "translated": "Не удалось создать новую личность.
Ошибка:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to generate a new identity.
Error:" + } + }, + { + "translated": "Вы действительно хотите создать новую личность и переопределить старую идентичность?", + "flags": [ + "google-translate" + ], + "key": { + "message": "Do you really want to generate a new identity and override the old identity?" + } + }, + { + "translated": "Пожалуйста, введите имя", + "flags": [ + "google-translate" + ], + "key": { + "message": "Please enter a name" + } + }, + { + "translated": "Пожалуйста, введите имя для нового профиля:
", + "flags": [ + "google-translate" + ], + "key": { + "message": "Please enter a name for the new profile:
" + } + }, + { + "translated": "Вы действительно хотите удалить этот профиль?", + "flags": [ + "google-translate" + ], + "key": { + "message": "Do you really want to delete this profile?" + } + }, + { + "translated": "Добавление бана% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Adding ban %o" + } + }, + { + "translated": "Не удалось добавить бан", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to add ban" + } + }, + { + "translated": "Запрет на редактирование% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Editing ban %o" + } + }, + { + "translated": "Применить редактировать изменения% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Apply edit changes %o" + } + }, + { + "translated": "Не удалось отредактировать бан", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to edit ban" + } + }, + { + "translated": "Удаление бана% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Deleting ban %o" + } + }, + { + "translated": "Не удалось удалить бан", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to delete ban" + } + }, + { + "translated": "Получил банлист:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got banlist: %o" + } + }, + { + "translated": "Отсутствует вход в бан с идентификатором% d", + "flags": [ + "google-translate" + ], + "key": { + "message": "Missing ban entry with id %d" + } + }, + { + "translated": "Banlist", + "flags": [ + "google-translate" + ], + "key": { + "message": "Banlist" + } + }, + { + "translated": "Поиск фильтра% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Search for filter %s" + } + }, + { + "translated": "Статус Отошёл с сообщением", + "flags": [ + "google-translate" + ], + "key": { + "message": "Set away message" + } + }, + { + "translated": "Пожалуйста, введите выездное сообщение", + "flags": [ + "google-translate" + ], + "key": { + "message": "Please enter the away message" + } + }, + { + "translated": "Канальный кодек не поддерживается", + "flags": [ + "google-translate" + ], + "key": { + "message": "Channel codec unsupported" + } + }, + { + "translated": "Этот канал имеет неподдерживаемый кодек.
Вы не можете говорить или слушать кого-либо на этом канале!", + "flags": [ + "google-translate" + ], + "key": { + "message": "This channel has an unsupported codec.
You cant speak or listen to anybody within this channel!" + } + }, + { + "translated": "Используйте токен / ключ привилегий", + "flags": [ + "google-translate" + ], + "key": { + "message": "Use token" + } + }, + { + "translated": "Пожалуйста, введите свой токен / ключ привилегий", + "flags": [ + "google-translate" + ], + "key": { + "message": "Please enter your token/priviledge key" + } + }, + { + "translated": "Токен успешно используется!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Toke successfully used!" + } + }, + { + "translated": "Не реализованы", + "flags": [ + "google-translate" + ], + "key": { + "message": "Not implemented" + } + }, + { + "translated": "Список токенов еще не реализован!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Token list is not implemented yet!" + } + }, + { + "translated": "У вас нет разрешения", + "flags": [ + "google-translate" + ], + "key": { + "message": "You dont have the permission" + } + }, + { + "translated": "У вас нет разрешения на просмотр списка банов", + "flags": [ + "google-translate" + ], + "key": { + "message": "You dont have the permission to view the ban list" + } + }, + { + "translated": "Введите название закладки", + "flags": [ + "google-translate" + ], + "key": { + "message": "Enter bookmarks name" + } + }, + { + "translated": "Пожалуйста, введите название закладки:
", + "flags": [ + "google-translate" + ], + "key": { + "message": "Please enter the bookmarks name:
" + } + }, + { + "translated": "Вы должны быть на связи", + "flags": [ + "google-translate" + ], + "key": { + "message": "You have to be connected" + } + }, + { + "translated": "Вы должны быть на связи!", + "flags": [ + "google-translate" + ], + "key": { + "message": "You have to be connected!" + } + }, + { + "translated": "У вас нет разрешения на создание запроса входа на сервер", + "flags": [ + "google-translate" + ], + "key": { + "message": "You dont have the permission to create a server query login" + } + }, + { + "translated": "Вы должны быть подключены, чтобы использовать эту функцию!", + "flags": [ + "google-translate" + ], + "key": { + "message": "You have to be connected to use this function!" + } + }, + { + "translated": "Начать соединение с% s:% d", + "flags": [ + "google-translate" + ], + "key": { + "message": "Start connection to %s:%d" + } + }, + { + "translated": "Ошибка при хешировании пароля", + "flags": [ + "google-translate" + ], + "key": { + "message": "Error while hashing password" + } + }, + { + "translated": "Не удалось хэшировать пароль сервера!
", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to hash server password!
" + } + }, + { + "translated": "Тип кодирования кодека не поддерживается!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Codec encode type not supported!" + } + }, + { + "translated": "Сбой соединения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Connect failed" + } + }, + { + "translated": "Не удалось подключиться к удаленному хосту! Исключение:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not connect to remote host! Exception: %o" + } + }, + { + "translated": "Не может подключиться", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not connect" + } + }, + { + "translated": "Не удалось подключиться к удаленному хосту (соединение отклонено)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not connect to remote host (Connection refused)" + } + }, + { + "translated": "Не удалось обработать рукопожатие:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to process handshake: %o" + } + }, + { + "translated": "Не удалось обработать рукопожатие:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to process handshake: " + } + }, + { + "translated": "Потеряна связь с удаленным сервером!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Lost connection to remote server!" + } + }, + { + "translated": "Соединение закрыто", + "flags": [ + "google-translate" + ], + "key": { + "message": "Connection closed" + } + }, + { + "translated": "Соединение было закрыто удаленным хостом", + "flags": [ + "google-translate" + ], + "key": { + "message": "The connection was closed by remote host" + } + }, + { + "translated": "Тайм-аут соединения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Connection ping timeout" + } + }, + { + "translated": "Соединение потеряно", + "flags": [ + "google-translate" + ], + "key": { + "message": "Connection lost" + } + }, + { + "translated": "Потерянное соединение с удаленным хостом (таймаут Ping)
Даже возможно?", + "flags": [ + "google-translate" + ], + "key": { + "message": "Lost connection to remote host (Ping timeout)
Even possible?" + } + }, + { + "translated": "Сервер закрыт ({0})", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server closed ({0})" + } + }, + { + "translated": "Сервер закрыт", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server closed" + } + }, + { + "translated": "Сервер требует пароль", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server requires password" + } + }, + { + "translated": "Пароль сервера", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server password" + } + }, + { + "translated": "Введите пароль сервера:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Enter server password:" + } + }, + { + "translated": "Вас выгнали с сервера {0} {1}", + "flags": [ + "google-translate" + ], + "key": { + "message": "You got kicked from the server by {0}{1}" + } + }, + { + "translated": "Вы забанены на сервере {0} {1}", + "flags": [ + "google-translate" + ], + "key": { + "message": "You got banned from the server by {0}{1}" + } + }, + { + "translated": "Получил непослушное отключение!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got uncaught disconnect!" + } + }, + { + "translated": "Тип:% o Данные:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Type: %o Data:" + } + }, + { + "translated": "Разрешено автоматическое переподключение, но невозможно переподключение, потому что у нас нет никакой информации ...", + "flags": [ + "google-translate" + ], + "key": { + "message": "Allowed to auto reconnect but cant reconnect because we dont have any information left..." + } + }, + { + "translated": "Повторное подключение через 5 секунд", + "flags": [ + "google-translate" + ], + "key": { + "message": "Reconnecting in 5 seconds" + } + }, + { + "translated": "Разрешено автоматическое переподключение. Переподключение через 5000 мс", + "flags": [ + "google-translate" + ], + "key": { + "message": "Allowed to auto reconnect. Reconnecting in 5000ms" + } + }, + { + "translated": "Воссоединение ...", + "flags": [ + "google-translate" + ], + "key": { + "message": "Reconnecting..." + } + }, + { + "translated": "Переподключение отменено", + "flags": [ + "google-translate" + ], + "key": { + "message": "Reconnect canceled" + } + }, + { + "translated": "Создать новый файл для загрузки в% s:% s (ключ:% s, ожидается% d байт)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Create new file download to %s:%s (Key: %s, Expect %d bytes)" + } + }, + { + "translated": "Есть данные, но сокет закрыт?", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got data, but socket closed?" + } + }, + { + "translated": "произошла ошибка", + "flags": [ + "google-translate" + ], + "key": { + "message": "an error occurent" + } + }, + { + "translated": "неожиданное закрытие (дистанционное закрытие)", + "flags": [ + "google-translate" + ], + "key": { + "message": "unexpected close (remote closed)" + } + }, + { + "translated": "Неверная запись в списке файлов. Путь:% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Invalid file list entry. Path: %s" + } + }, + { + "translated": "Неверный конец списка файлов. Дорожка:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Invalid file list entry finish. Path: " + } + }, + { + "translated": "Не удалось загрузить значок% s ->% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not download icon %s -> %s" + } + }, + { + "translated": "Не удалось загрузить значок {0}. ({1})", + "flags": [ + "google-translate" + ], + "key": { + "message": "Fail to download icon {0}. ({1})" + } + }, + { + "translated": "Ошибка при загрузке иконки! (% S)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Error while downloading icon! (%s)" + } + }, + { + "translated": "Не удалось запросить загрузку для значка {0}. ({1})", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to request download for icon {0}. ({1})" + } + }, + { + "translated": "Значок имеет тип изображения% o (носитель:% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Icon has an image type of %o (media: %o)" + } + }, + { + "translated": "Значок% o загружен :)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Icon %o loaded :)" + } + }, + { + "translated": "Не удалось загрузить значок% o. Причина:% p", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not load icon %o. Reason: %p" + } + }, + { + "translated": "Загрузка аватара% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Downloading avatar %s" + } + }, + { + "translated": "Не удалось загрузить аватар% o ->% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not download avatar %o -> %s" + } + }, + { + "translated": "Не удалось загрузить аватар для {0}. ({1})", + "flags": [ + "google-translate" + ], + "key": { + "message": "Fail to download avatar for {0}. ({1})" + } + }, + { + "translated": "Ошибка при загрузке аватара! (% S)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Error while downloading avatar! (%s)" + } + }, + { + "translated": "Не удалось запросить загрузку аватара для {0}. ({1})", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to request avatar download for {0}. ({1})" + } + }, + { + "translated": "аватар имеет тип изображения% o (медиа:% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "avatar has an image type of %o (media: %o)" + } + }, + { + "translated": "Аватар имеет тип изображения% o (медиа:% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Avatar has an image type of %o (media: %o)" + } + }, + { + "translated": "Не удалось загрузить аватар для% s. Причина:% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not load avatar for %s. Reason: %s" + } + }, + { + "translated": "Не удалось загрузить аватар", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not load avatar " + } + }, + { + "translated": "сдвиг", + "flags": [ + "google-translate" + ], + "key": { + "message": "Shift" + } + }, + { + "translated": "все", + "flags": [ + "google-translate" + ], + "key": { + "message": "Alt" + } + }, + { + "translated": "CTRL", + "flags": [ + "google-translate" + ], + "key": { + "message": "CTRL" + } + }, + { + "translated": "Выиграть", + "flags": [ + "google-translate" + ], + "key": { + "message": "Win" + } + }, + { + "translated": "снята с охраны", + "flags": [ + "google-translate" + ], + "key": { + "message": "unset" + } + }, + { + "translated": "Сообщение для форматирования содержит недопустимый индекс (% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Message to format contains invalid index (%o)" + } + }, + { + "translated": "Ошибка разбора BBCode:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "BBCode parse error: %o" + } + }, + { + "translated": "Очистить", + "flags": [ + "google-translate" + ], + "key": { + "message": "Clear" + } + }, + { + "translated": "Закройте все личные вкладки", + "flags": [ + "google-translate" + ], + "key": { + "message": "Close all private tabs" + } + }, + { + "translated": "Не удалось отправить сообщение о пении (не подключен)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not send chant message (Not connected)" + } + }, + { + "translated": "Не удалось отправить текстовое сообщение.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to send text message." + } + }, + { + "translated": "Не удалось отправить текстовое сообщение с сервера:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to send server text message: %o" + } + }, + { + "translated": "Серверный чат", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server chat" + } + }, + { + "translated": "Не удалось отправить текстовое сообщение канала:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to send channel text message: %o" + } + }, + { + "translated": "Канал чат", + "flags": [ + "google-translate" + ], + "key": { + "message": "Channel chat" + } + }, + { + "translated": "Сообщение чата содержит URL:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Chat message contains URL: %o" + } + }, + { + "translated": "Создать новое соединение", + "flags": [ + "google-translate" + ], + "key": { + "message": "Create a new connection" + } + }, + { + "translated": "соединять", + "flags": [ + "google-translate" + ], + "key": { + "message": "Connect" + } + }, + { + "translated": "Редактировать в", + "flags": [ + "google-translate" + ], + "key": { + "message": "Edit ban" + } + }, + { + "translated": "Добавить бан", + "flags": [ + "google-translate" + ], + "key": { + "message": "Add ban" + } + }, + { + "translated": "да", + "flags": [ + "google-translate" + ], + "key": { + "message": "Yes" + } + }, + { + "translated": "нет", + "flags": [ + "google-translate" + ], + "key": { + "message": "No" + } + }, + { + "translated": "Не удалось инициализировать систему перевода! Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to initialized the translation system!\nError: %o" + } + }, + { + "translated": "Не удалось настроить jsrender", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to setup jsrender" + } + }, + { + "translated": "Не удалось загрузить jsrender! % о", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to load jsrender! %o" + } + }, + { + "translated": "Не удалось настроить главную страницу!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to setup main page!" + } + }, + { + "translated": "Не удалось инициализировать ppt! Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to initialize ppt!\nError: %o" + } + }, + { + "translated": "Не удалось инициализировать ppt!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to initialize ppt!" + } + }, + { + "translated": "Инициализируйте аудио контроллер позже!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Initialize audio controller later!" + } + }, + { + "translated": "Отсутствует audio.player.initializeFromGesture", + "flags": [ + "google-translate" + ], + "key": { + "message": "Missing audio.player.initializeFromGesture" + } + }, + { + "translated": "Инициализация статистики с помощью этой конфигурации:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Initializing statistics with this config: %o" + } + }, + { + "translated": "Потеряно соединение с сервером статистики (Соединение закрыто). Причина:% o. Объект события:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Lost connection to statistics server (Connection closed). Reason: %o. Event object: %o" + } + }, + { + "translated": "Успешно подключен к серверу. Инициализация сессии.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Successfully connected to server. Initializing session." + } + }, + { + "translated": "Получил ошибку. Закрытие соединения. Объект:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Received an error. Closing connection. Object: %o" + } + }, + { + "translated": "Получил сообщение, которое не является строкой. Объект события:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Received an message which isn't a string. Event object: %o" + } + }, + { + "translated": "Запланированное переподключение в% dms", + "flags": [ + "google-translate" + ], + "key": { + "message": "Scheduled reconnect in %dms" + } + }, + { + "translated": "Воссоединение", + "flags": [ + "google-translate" + ], + "key": { + "message": "Reconnecting" + } + }, + { + "translated": "Обработка сообщения типа% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Handling message of type %s" + } + }, + { + "translated": "Получено сообщение с неизвестным типом (% s). Удаление сообщения. Полное сообщение:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Received message with an unknown type (%s). Dropping message. Full message: %o" + } + }, + { + "translated": "Сессия успешно инициализирована.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Session successfully initialized." + } + }, + { + "translated": "Не удалось пересчитать данные PCM для кодека. Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not resample PCM data for codec. Error: %o" + } + }, + { + "translated": "Не удалось кодировать данные PCM для кодека. Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not encode PCM data for codec. Error: %o" + } + }, + { + "translated": "Требуемое время:% d", + "flags": [ + "google-translate" + ], + "key": { + "message": "Required time: %d" + } + }, + { + "translated": "Время хранения рабочего сообщения:% d", + "flags": [ + "google-translate" + ], + "key": { + "message": "Worker message stock time: %d" + } + }, + { + "translated": "Неверный рабочий токен!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Invalid worker token!" + } + }, + { + "translated": "[Кодек] Получил рабочий ответ инициализации: Успешно:% o Сообщение:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "[Codec] Got worker init response: Success: %o Message: %o" + } + }, + { + "translated": "Костюм обратного вызова! (% О)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Costume callback! (%o)" + } + }, + { + "translated": "Неверный код возврата! (% О)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Invalid return code! (%o)" + } + }, + { + "translated": "Настройка голоса", + "flags": [ + "google-translate" + ], + "key": { + "message": "Setting up voice" + } + }, + { + "translated": "Настройка пропуска голоса (нет голосового моста)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Skipping voice setup (No voice bridge available)" + } + }, + { + "translated": "Подключено как {0}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Connected as {0}" + } + }, + { + "translated": "Неверный идентификатор заказа канала!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Invalid channel order id!" + } + }, + { + "translated": "Неверный родительский канал", + "flags": [ + "google-translate" + ], + "key": { + "message": "Invalid channel parent" + } + }, + { + "translated": "Получил% d новых каналов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got %d new channels" + } + }, + { + "translated": "Получил% d удаления каналов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got %d channel deletions" + } + }, + { + "translated": "Неверный канал onDelete (Неизвестный канал)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Invalid channel onDelete (Unknown channel)" + } + }, + { + "translated": "Получил% d скрытие канала", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got %d channel hides" + } + }, + { + "translated": "Неверный канал на скрытии (Неизвестный канал)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Invalid channel on hide (Unknown channel)" + } + }, + { + "translated": "{0} появился из {1} в {2}", + "flags": [ + "google-translate" + ], + "key": { + "message": "{0} appeared from {1} to {2}" + } + }, + { + "translated": "{0} подключен к каналу {1}", + "flags": [ + "google-translate" + ], + "key": { + "message": "{0} connected to channel {1}" + } + }, + { + "translated": "{0} появился из {1} в {2}, перемещен на {3}", + "flags": [ + "google-translate" + ], + "key": { + "message": "{0} appeared from {1} to {2}, moved by {3}" + } + }, + { + "translated": "{0} появился из {1} в {2}, выгнан {3} {4}", + "flags": [ + "google-translate" + ], + "key": { + "message": "{0} appeared from {1} to {2}, kicked by {3}{4}" + } + }, + { + "translated": "Неизвестная причина для% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unknown reasonid for %o" + } + }, + { + "translated": "Ваш партнер по чату переподключился", + "flags": [ + "google-translate" + ], + "key": { + "message": "Your chat partner has reconnected" + } + }, + { + "translated": "Неизвестный клиент ушел!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unknown client left!" + } + }, + { + "translated": "{0} исчез из {1} в {2}", + "flags": [ + "google-translate" + ], + "key": { + "message": "{0} disappeared from {1} to {2}" + } + }, + { + "translated": "{0} покинул сервер {1}", + "flags": [ + "google-translate" + ], + "key": { + "message": "{0} left the server{1}" + } + }, + { + "translated": "{0} был удален с сервера {1}. {2}", + "flags": [ + "google-translate" + ], + "key": { + "message": "{0} was kicked from the server by {1}.{2}" + } + }, + { + "translated": "{0} был удален с вашего канала {1}. {2}", + "flags": [ + "google-translate" + ], + "key": { + "message": "{0} was kicked from your channel by {1}.{2}" + } + }, + { + "translated": "{0} был заблокирован {1} {2}. {3}", + "flags": [ + "google-translate" + ], + "key": { + "message": "{0} was banned {1} by {2}.{3}" + } + }, + { + "translated": "Неизвестный клиент оставил причину!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unknown client left reason!" + } + }, + { + "translated": "Ваш партнер по чату отключился", + "flags": [ + "google-translate" + ], + "key": { + "message": "Your chat partner has disconnected" + } + }, + { + "translated": "Неизвестный ход клиента (Клиент)!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unknown client move (Client)!" + } + }, + { + "translated": "Перемещение неизвестного клиента (канал на)!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unknown client move (Channel to)!" + } + }, + { + "translated": "Перемещение неизвестного клиента (канал от)!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unknown client move (Channel from)!" + } + }, + { + "translated": "Вы были перемещены {3} из канала {1} в {2}", + "flags": [ + "google-translate" + ], + "key": { + "message": "You was moved by {3} from channel {1} to {2}" + } + }, + { + "translated": "{0} был перемещен из канала {1} в {2} на {3}", + "flags": [ + "google-translate" + ], + "key": { + "message": "{0} was moved from channel {1} to {2} by {3}" + } + }, + { + "translated": "Вы переключились с канала {1} на {2}", + "flags": [ + "google-translate" + ], + "key": { + "message": "You switched from channel {1} to {2}" + } + }, + { + "translated": "{0} переключен с канала {1} на {2}", + "flags": [ + "google-translate" + ], + "key": { + "message": "{0} switched from channel {1} to {2}" + } + }, + { + "translated": "Вы вышвырнуты из канала {1} на канал {2} {3} {4}", + "flags": [ + "google-translate" + ], + "key": { + "message": "You got kicked out of the channel {1} to channel {2} by {3}{4}" + } + }, + { + "translated": "{0} выгнан из канала {1} в {2} {3} {4}", + "flags": [ + "google-translate" + ], + "key": { + "message": "{0} got kicked from channel {1} to {2} by {3}{4}" + } + }, + { + "translated": "Неизвестная причина id% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unknown reason id %o" + } + }, + { + "translated": "Неизвестный канал движется (Канал)!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unknown channel move (Channel)!" + } + }, + { + "translated": "Неизвестный ход канала (предыдущий)!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unknown channel move (prev)!" + } + }, + { + "translated": "Неизвестный канал движется (родитель)!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unknown channel move (parent)!" + } + }, + { + "translated": "Редактирование неизвестного канала (Channel)!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unknown channel edit (Channel)!" + } + }, + { + "translated": "Получил личное сообщение от недействительного клиента!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got private message from invalid client!" + } + }, + { + "translated": "Полученный чат закрыт для неизвестного клиента", + "flags": [ + "google-translate" + ], + "key": { + "message": "Received chat close for unknown client" + } + }, + { + "translated": "Полученный чат закрыт для клиента, но уникальные идентификаторы не совпадают. (ожидаемый% o, полученный% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Received chat close for client, but unique ids dosn't match. (expected %o, received %o)" + } + }, + { + "translated": "Получен чат для клиента, но у нас нет открытого чата.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Received chat close for client, but we haven't a chat open." + } + }, + { + "translated": "Ваш собеседник закрыл разговор", + "flags": [ + "google-translate" + ], + "key": { + "message": "Your chat partner has close the conversation" + } + }, + { + "translated": "Пытался обновить несуществующий клиент", + "flags": [ + "google-translate" + ], + "key": { + "message": "Tried to update an non existing client" + } + }, + { + "translated": "Получил информацию о музыкальном проигрывателе для неизвестного или недействительного бота! (ID:% i, запись:% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got music player info for unknown or invalid bot! (ID: %i, Entry: %o)" + } + }, + { + "translated": "Полученный канал подписан на невидимый канал (cid:% d)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Received channel subscribed for not visible channel (cid: %d)" + } + }, + { + "translated": "Полученный канал отписался от невидимого канала (cid:% d)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Received channel unsubscribed for not visible channel (cid: %d)" + } + }, + { + "translated": "Не удалось проанализировать запись плейлиста:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to parse playlist entry: %o" + } + }, + { + "translated": "Получено недействительное уведомление для песен плейлиста", + "flags": [ + "google-translate" + ], + "key": { + "message": "Received invalid notification for playlist songs" + } + }, + { + "translated": "Не удалось проанализировать запись песни плейлиста:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to parse playlist song entry: %o" + } + }, + { + "translated": "Получено недействительное уведомление для информации о плейлисте", + "flags": [ + "google-translate" + ], + "key": { + "message": "Received invalid notification for playlist info" + } + }, + { + "translated": "Не удалось проанализировать информацию о плейлисте:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to parse playlist info: %o" + } + }, + { + "translated": "Не удалось получить версию:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to get version:" + } + }, + { + "translated": "Гнездо подключено", + "flags": [ + "google-translate" + ], + "key": { + "message": "Socket connected" + } + }, + { + "translated": "Вход в систему...", + "flags": [ + "google-translate" + ], + "key": { + "message": "Logging in..." + } + }, + { + "translated": "Не удалось правильно закрыть старое соединение. Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to close old connection properly. Error: %o" + } + }, + { + "translated": "Соединение с {0}: {1}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Connecting to {0}:{1}" + } + }, + { + "translated": "Тайм-аут подключения сработал!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Connect timeout triggered!" + } + }, + { + "translated": "Не удалось закрыть соединение после срабатывания тайм-аута! (% О)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to close connection after timeout had been triggered! (%o)" + } + }, + { + "translated": "Получена ошибка веб-сокета: (% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Received web socket error: (%o)" + } + }, + { + "translated": "Не удалось закрыть соединение после неудачной попытки соединения (% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to close connection after connect attempt failed (%o)" + } + }, + { + "translated": "запрос на отключение", + "flags": [ + "google-translate" + ], + "key": { + "message": "request disconnect" + } + }, + { + "translated": "Не удалось разобрать сообщение json!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not parse message json!" + } + }, + { + "translated": "Отсутствует тип данных!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Missing data type!" + } + }, + { + "translated": "Команда обработки "% s"", + "flags": [ + "google-translate" + ], + "key": { + "message": "Handling command '%s'" + } + }, + { + "translated": "Json:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Json:" + } + }, + { + "translated": "Удаление пакета команд WebRTC, потому что у нас нет моста.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Dropping WebRTC command packet, because we havent a bridge." + } + }, + { + "translated": "Неизвестный тип команды% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unknown command type %o" + } + }, + { + "translated": "Получено неизвестное сообщение типа% s. Удаление сообщения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Received unknown message of type %s. Dropping message" + } + }, + { + "translated": "Пытался отправить по неверному сокету (% s)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Tried to send on a invalid socket (%s)" + } + }, + { + "translated": "Пытался отправить команду без действительного соединения.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Tried to send a command without a valid connection." + } + }, + { + "translated": "Нет соединения", + "flags": [ + "google-translate" + ], + "key": { + "message": "not connected" + } + }, + { + "translated": "Недостаточные разрешения клиента. Не удалось на разрешение", + "flags": [ + "google-translate" + ], + "key": { + "message": "Insufficient client permissions. Failed on permission " + } + }, + { + "translated": "Недостаточные разрешения клиента. Не удалось получить разрешение {}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Insufficient client permissions. Failed on permission {}" + } + }, + { + "translated": "Результатом выполнения команды является", + "flags": [ + "google-translate" + ], + "key": { + "message": "Command execution results in " + } + }, + { + "translated": "Неверный тип результата обещания:% o. Результат:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Invalid promise result type: %o. Result:" + } + }, + { + "translated": "неизвестная страна", + "flags": [ + "google-translate" + ], + "key": { + "message": "unknown country" + } + }, + { + "translated": "Не удалось загрузить профиль. Причина:% s, данные профиля:% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to load profile. Reason: %s, Profile data: %s" + } + }, + { + "translated": "Не удалось сгенерировать личность по умолчанию", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to generate default identity" + } + }, + { + "translated": "Не удалось сгенерировать личность по умолчанию!
Пожалуйста, создайте личность вручную в настройках => профили", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to generate default identity!
Please manually generate the identity within your settings => profiles" + } + }, + { + "translated": "Не удалось инициализировать рукопожатие на основе имени. Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to initialize name based handshake. Error: %o" + } + }, + { + "translated": "Не удалось инициализировать рукопожатие на основе TeaForum. Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to initialize TeaForum based handshake. Error: %o" + } + }, + { + "translated": "Не удалось доказать личность. Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to proof the identity. Error: %o" + } + }, + { + "translated": "Не удалось инициализировать рукопожатие на основе TeamSpeak. Ошибка:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to initialize TeamSpeak based handshake. Error: %o" + } + }, + { + "translated": "Ошибка работника военнопленного% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "POW Worker error %o" + } + }, + { + "translated": "Не удалось завершить работу с военнопленным! (% О)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to finalize POW worker! (%o)" + } + }, + { + "translated": "Полученное сообщение:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Received message: %o" + } + }, + { + "translated": "Не удалось сгенерировать новый ключ:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Could not generate a new key: %o" + } + }, + { + "translated": "Отсутствует звук% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Missing sound %o" + } + }, + { + "translated": "Пытался воспроизвести звук без звукового контекста (Звук:% o). Отбрасывание воспроизведения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Tried to replay a sound without an audio context (Sound: %o). Dropping playback" + } + }, + { + "translated": "Воспроизведение звука% s (Громкость звука:% o | Общая громкость% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Replaying sound %s (Sound volume: %o | Master volume %o)" + } + }, + { + "translated": "Сброс запрошенного воспроизведения для звука% s, потому что он будет перекрываться", + "flags": [ + "google-translate" + ], + "key": { + "message": "Dropping requested playback for sound %s because it would overlap." + } + }, + { + "translated": "Использование кэшированного буфера:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Using cached buffer: %o" + } + }, + { + "translated": "Данные декодирования", + "flags": [ + "google-translate" + ], + "key": { + "message": "Decoding data" + } + }, + { + "translated": "Получил декодированные данные", + "flags": [ + "google-translate" + ], + "key": { + "message": "Got decoded data" + } + }, + { + "translated": "Не удалось декодировать аудиоданные для% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to decode audio data for %o" + } + }, + { + "translated": "Не удалось загрузить аудиофайл. (Код ответа% o)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to load audio file. (Response code %o)" + } + }, + { + "translated": "Не удалось загрузить аудиофайл", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to load audio file " + } + }, + { + "translated": "Воспроизведение% s", + "flags": [ + "google-translate" + ], + "key": { + "message": "Replaying %s" + } + }, + { + "translated": "Ваш браузер не поддерживает decodeAudioData! Использование узла для воспроизведения! Это обходит вывод звука и регулировку громкости!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Your browser does not support decodeAudioData! Using a node to playback! This bypasses the audio output and volume regulation!" + } + }, + { + "translated": "Показать информацию о сервере", + "flags": [ + "google-translate" + ], + "key": { + "message": "Show server info" + } + }, + { + "translated": "редактировать", + "flags": [ + "google-translate" + ], + "key": { + "message": "Edit" + } + }, + { + "translated": "Изменение свойств сервера% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Changing server properties %o" + } + }, + { + "translated": "Измененные свойства:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Changed properties: %o" + } + }, + { + "translated": "Пригласить приятеля", + "flags": [ + "google-translate" + ], + "key": { + "message": "Invite buddy" + } + }, + { + "translated": "URL приглашенного друга", + "flags": [ + "google-translate" + ], + "key": { + "message": "Buddy invite URL" + } + }, + { + "translated": "URL приглашения вашего собеседника:
", + "flags": [ + "google-translate" + ], + "key": { + "message": "Your buddy invite URL:
" + } + }, + { + "translated": " Это было скопировано в ваш буфер обмена.", + "flags": [ + "google-translate" + ], + "key": { + "message": "This has been copied to your clipboard." + } + }, + { + "translated": "Обновить свойства (% i)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Update properties (%i)" + } + }, + { + "translated": "Управление закладками", + "flags": [ + "google-translate" + ], + "key": { + "message": "Manage bookmarks" + } + }, + { + "translated": "Создать новую запись", + "flags": [ + "google-translate" + ], + "key": { + "message": "Create a new entry" + } + }, + { + "translated": "Вы действительно хотите удалить эту запись?", + "flags": [ + "google-translate" + ], + "key": { + "message": "Do you really want to delete this entry?" + } + }, + { + "translated": "Расходная группа", + "flags": [ + "google-translate" + ], + "key": { + "message": "Expend group" + } + }, + { + "translated": "Расходовать все", + "flags": [ + "google-translate" + ], + "key": { + "message": "Expend all" + } + }, + { + "translated": "Свернуть группу", + "flags": [ + "google-translate" + ], + "key": { + "message": "Collapse group" + } + }, + { + "translated": "Свернуть все", + "flags": [ + "google-translate" + ], + "key": { + "message": "Collapse all" + } + }, + { + "translated": "Добавить разрешение", + "flags": [ + "google-translate" + ], + "key": { + "message": "Add permission" + } + }, + { + "translated": "Удалить разрешение", + "flags": [ + "google-translate" + ], + "key": { + "message": "Remove permission" + } + }, + { + "translated": "Добавить разрешение на грант", + "flags": [ + "google-translate" + ], + "key": { + "message": "Add grant permission" + } + }, + { + "translated": "Удалить разрешение на выдачу", + "flags": [ + "google-translate" + ], + "key": { + "message": "Remove grant permission" + } + }, + { + "translated": "Показать описание разрешения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Show permission description" + } + }, + { + "translated": "Описание разрешения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Permission description" + } + }, + { + "translated": "Описание разрешения для разрешения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Permission description for permission " + } + }, + { + "translated": "Копировать имя разрешения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Copy permission name" + } + }, + { + "translated": "Разрешения сервера", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server Permissions" + } + }, + { + "translated": "Удаление разрешения клиентского канала% s. allow.id:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Removing client channel permission %s. permission.id: %o" + } + }, + { + "translated": "Удаление клиентского канала дает разрешение% s. allow.id:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Removing client channel grant permission %s. permission.id: %o" + } + }, + { + "translated": "Добавление или обновление клиентского разрешения канала% s. разрешение. {id:% o, значение:% o, flag_skip:% o, flag_negate:% o}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Adding or updating client channel permission %s. permission.{id: %o, value: %o, flag_skip: %o, flag_negate: %o}" + } + }, + { + "translated": "Добавление или обновление клиентского канала предоставляет разрешение% s. разрешение. {id:% o, значение:% o}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Adding or updating client channel grant permission %s. permission.{id: %o, value: %o}" + } + }, + { + "translated": "Удаление разрешения клиента% s. allow.id:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Removing client permission %s. permission.id: %o" + } + }, + { + "translated": "Удаление клиентского разрешения на получение разрешения% s. allow.id:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Removing client grant permission %s. permission.id: %o" + } + }, + { + "translated": "Добавление или обновление клиентского разрешения% s. разрешение. {id:% o, значение:% o, flag_skip:% o, flag_negate:% o}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Adding or updating client permission %s. permission.{id: %o, value: %o, flag_skip: %o, flag_negate: %o}" + } + }, + { + "translated": "Добавление или обновление клиентского разрешения на получение разрешения% s. разрешение. {id:% o, значение:% o}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Adding or updating client grant permission %s. permission.{id: %o, value: %o}" + } + }, + { + "translated": "Удаление разрешения канала% s. allow.id:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Removing channel permission %s. permission.id: %o" + } + }, + { + "translated": "Удаление разрешения на получение канала% s. allow.id:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Removing channel grant permission %s. permission.id: %o" + } + }, + { + "translated": "Добавление или обновление разрешения канала% s. разрешение. {id:% o, значение:% o, flag_skip:% o, flag_negate:% o}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Adding or updating channel permission %s. permission.{id: %o, value: %o, flag_skip: %o, flag_negate: %o}" + } + }, + { + "translated": "Добавление или обновление разрешения на предоставление канала% s. разрешение. {id:% o, значение:% o}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Adding or updating channel grant permission %s. permission.{id: %o, value: %o}" + } + }, + { + "translated": "Удаление разрешения группы каналов% s. allow.id:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Removing channel group permission %s. permission.id: %o" + } + }, + { + "translated": "Удаление разрешения для группы каналов% s. allow.id:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Removing channel group grant permission %s. permission.id: %o" + } + }, + { + "translated": "Добавление или обновление разрешения группы каналов% s. разрешение. {id:% o, значение:% o, flag_skip:% o, flag_negate:% o}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Adding or updating channel group permission %s. permission.{id: %o, value: %o, flag_skip: %o, flag_negate: %o}" + } + }, + { + "translated": "Добавление или обновление разрешения для группы каналов%%. разрешение. {id:% o, значение:% o}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Adding or updating channel group grant permission %s. permission.{id: %o, value: %o}" + } + }, + { + "translated": "Удаление разрешения группы серверов% s. allow.id:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Removing server group permission %s. permission.id: %o" + } + }, + { + "translated": "Удаление группы серверов дает разрешение% s. allow.id:% o", + "flags": [ + "google-translate" + ], + "key": { + "message": "Removing server group grant permission %s. permission.id: %o" + } + }, + { + "translated": "Добавление или обновление разрешения группы серверов% s. разрешение. {id:% o, значение:% o, flag_skip:% o, flag_negate:% o}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Adding or updating server group permission %s. permission.{id: %o, value: %o, flag_skip: %o, flag_negate: %o}" + } + }, + { + "translated": "Добавление или обновление группы серверов дает разрешение% s. разрешение. {id:% o, значение:% o}", + "flags": [ + "google-translate" + ], + "key": { + "message": "Adding or updating server group grant permission %s. permission.{id: %o, value: %o}" + } + }, + { + "translated": "Информация о песне", + "flags": [ + "google-translate" + ], + "key": { + "message": "Song info" + } + }, + { + "translated": "Добавить песню", + "flags": [ + "google-translate" + ], + "key": { + "message": "Add a song" + } + }, + { + "translated": "Изменить плейлист", + "flags": [ + "google-translate" + ], + "key": { + "message": "Edit playlist" + } + }, + { + "translated": "Не удалось изменить свойства.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to change properties." + } + }, + { + "translated": "Не удалось изменить свойства списка воспроизведения.
Ошибка:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to change playlist properties.
Error: " + } + }, + { + "translated": "Не удалось изменить разрешение.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to change permission." + } + }, + { + "translated": "Не удалось изменить разрешения списка воспроизведения.
Ошибка:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to change playlist permissions.
Error: " + } + }, + { + "translated": "Вы действительно хотите отменить все свои изменения?", + "flags": [ + "google-translate" + ], + "key": { + "message": "Do you really want to discard all your changes?" + } + }, + { + "translated": "загрузка списка песен", + "flags": [ + "google-translate" + ], + "key": { + "message": "loading song list" + } + }, + { + "translated": "Не удалось удалить песню.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to remove song." + } + }, + { + "translated": "Не удалось удалить песню / URL из списка воспроизведения.
Ошибка:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to remove song/url from the playlist.
Error: " + } + }, + { + "translated": "не удалось загрузить список песен", + "flags": [ + "google-translate" + ], + "key": { + "message": "failed to load song list" + } + }, + { + "translated": "Не удалось добавить песню.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to add song." + } + }, + { + "translated": "Не удалось добавить песню / URL в плейлист.
Ошибка:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to add song/url to the playlist.
Error: " + } + }, + { + "translated": "Не удалось запросить информацию о плейлисте", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to query playlist info" + } + }, + { + "translated": "Не удалось запросить информацию о плейлисте.
Ошибка:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to query playlist info.
Error:" + } + }, + { + "translated": "Управление плейлистами", + "flags": [ + "google-translate" + ], + "key": { + "message": "Manage playlists" + } + }, + { + "translated": "Плейлист создан успешно", + "flags": [ + "google-translate" + ], + "key": { + "message": "Playlist created successful" + } + }, + { + "translated": "Плейлист успешно создан.
Должны ли мы открыть редактор?", + "flags": [ + "google-translate" + ], + "key": { + "message": "The playlist has been successfully created.
Should we open the editor?" + } + }, + { + "translated": "Невозможно создать плейлист", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unable to create playlist" + } + }, + { + "translated": "Не удалось создать плейлист
Сообщение:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to create playlist
Message: " + } + }, + { + "translated": "Вы действительно хотите удалить этот плейлист?", + "flags": [ + "google-translate" + ], + "key": { + "message": "Do you really want to delete this playlist?" + } + }, + { + "translated": "Плейлист удален успешно", + "flags": [ + "google-translate" + ], + "key": { + "message": "Playlist deleted successful" + } + }, + { + "translated": "Этот плейлист был успешно удален.", + "flags": [ + "google-translate" + ], + "key": { + "message": "This playlist has been deleted successfully." + } + }, + { + "translated": "Невозможно удалить плейлист", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unable to delete playlist" + } + }, + { + "translated": "Не удалось удалить плейлист
Сообщение:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to delete playlist
Message: " + } + }, + { + "translated": "Вы ткнули!", + "flags": [ + "google-translate" + ], + "key": { + "message": "You have been poked!" + } + }, + { + "translated": "Создать запрос к серверу", + "flags": [ + "google-translate" + ], + "key": { + "message": "Create a server query login" + } + }, + { + "translated": "Неверное имя пользователя", + "flags": [ + "google-translate" + ], + "key": { + "message": "Invalid username" + } + }, + { + "translated": "Пожалуйста, введите верное имя!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Please enter a valid name!" + } + }, + { + "translated": "Невозможно создать аккаунт", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unable to create account" + } + }, + { + "translated": "Не удалось создать аккаунт
Сообщение:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to create account
Message: " + } + }, + { + "translated": "Учетные данные запроса к серверу", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server query credentials" + } + }, + { + "translated": "Новые учетные данные сервера", + "flags": [ + "google-translate" + ], + "key": { + "message": "New server query credentials" + } + }, + { + "translated": "Управление учетными записями запросов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Manage query accounts" + } + }, + { + "translated": "Изменить имя аккаунта", + "flags": [ + "google-translate" + ], + "key": { + "message": "Change account name" + } + }, + { + "translated": "Введите новое имя для логина:
", + "flags": [ + "google-translate" + ], + "key": { + "message": "Enter the new name for the login:
" + } + }, + { + "translated": "Невозможно переименовать аккаунт", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unable to rename account" + } + }, + { + "translated": "Не удалось переименовать аккаунт
Сообщение:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to rename account
Message: " + } + }, + { + "translated": "Аккаунт успешно переименован", + "flags": [ + "google-translate" + ], + "key": { + "message": "Account successfully renamed" + } + }, + { + "translated": "Учетная запись запроса была переименована!", + "flags": [ + "google-translate" + ], + "key": { + "message": "The query account has been renamed!" + } + }, + { + "translated": "Изменить пароль аккаунта", + "flags": [ + "google-translate" + ], + "key": { + "message": "Change account's password" + } + }, + { + "translated": "Введите новый пароль (оставьте пустым для автогенерации):
", + "flags": [ + "google-translate" + ], + "key": { + "message": "Enter a new password (leave blank for auto generation):
" + } + }, + { + "translated": "Невозможно изменить пароль", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unable to change password" + } + }, + { + "translated": "Не удалось сменить пароль
Сообщение:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to change password
Message: " + } + }, + { + "translated": "Вы действительно хотите удалить этот аккаунт?", + "flags": [ + "google-translate" + ], + "key": { + "message": "Do you really want to delete this account?" + } + }, + { + "translated": "Невозможно удалить аккаунт", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unable to delete account" + } + }, + { + "translated": "Не удалось удалить аккаунт
Сообщение:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to delete account
Message: " + } + }, + { + "translated": "Аккаунт успешно удален", + "flags": [ + "google-translate" + ], + "key": { + "message": "Account successfully deleted" + } + }, + { + "translated": "Учетная запись запроса была успешно удалена!", + "flags": [ + "google-translate" + ], + "key": { + "message": "The query account has been successfully deleted!" + } + }, + { + "translated": "Целевая частота дискретизации находится за пределами диапазона [3000, 384000].", + "flags": [ + "google-translate" + ], + "key": { + "message": "The target sample rate is outside the range [3000, 384000]." + } + }, + { + "translated": "Получен пустой буфер в качестве входных данных! Возвращаем пустой вывод!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Received empty buffer as input! Returning empty output!" + } + }, + { + "translated": "Подключиться к серверу", + "flags": [ + "google-translate" + ], + "key": { + "message": "Connect to a server" + } + }, + { + "translated": "Отключиться от сервера", + "flags": [ + "google-translate" + ], + "key": { + "message": "Disconnect from server" + } + }, + { + "translated": "закладки", + "flags": [ + "google-translate" + ], + "key": { + "message": "Bookmarks" + } + }, + { + "translated": "Добавить текущий сервер в закладки", + "flags": [ + "google-translate" + ], + "key": { + "message": "Add current server to bookmarks" + } + }, + { + "translated": "Удалить текущий сервер в закладки", + "flags": [ + "google-translate" + ], + "key": { + "message": "Remove current server to bookmarks" + } + }, + { + "translated": "Статус Отошёл", + "flags": [ + "google-translate" + ], + "key": { + "message": "Toggle away status" + } + }, + { + "translated": "Отключить / включить микрофон", + "flags": [ + "google-translate" + ], + "key": { + "message": "Mute/unmute microphone" + } + }, + { + "translated": "Отключить / включить наушники", + "flags": [ + "google-translate" + ], + "key": { + "message": "Mute/unmute headphones" + } + }, + { + "translated": "Настройки звука", + "flags": [ + "google-translate" + ], + "key": { + "message": "Audio settings" + } + }, + { + "translated": "Переключить режим подписки на канал", + "flags": [ + "google-translate" + ], + "key": { + "message": "Toggle channel subscribe mode" + } + }, + { + "translated": "Список токенов", + "flags": [ + "google-translate" + ], + "key": { + "message": "List tokens" + } + }, + { + "translated": "Серверные инструменты", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server tools" + } + }, + { + "translated": "Плейлисты", + "flags": [ + "google-translate" + ], + "key": { + "message": "Playlists" + } + }, + { + "translated": "Просмотр / редактирование разрешений", + "flags": [ + "google-translate" + ], + "key": { + "message": "View/edit permissions" + } + }, + { + "translated": "Показать / скрыть запросы к серверу", + "flags": [ + "google-translate" + ], + "key": { + "message": "Show/hide server queries" + } + }, + { + "translated": "Управление запросами к серверу", + "flags": [ + "google-translate" + ], + "key": { + "message": "Manage server queries" + } + }, + { + "translated": "Создать запрос к серверу", + "flags": [ + "google-translate" + ], + "key": { + "message": "Create server query login" + } + }, + { + "translated": "Изменить глобальные настройки клиента", + "flags": [ + "google-translate" + ], + "key": { + "message": "Edit global client settings" + } + }, + { + "translated": "введите сообщение чата ...", + "flags": [ + "google-translate" + ], + "key": { + "message": "enter a chat message..." + } + }, + { + "translated": "отправить", + "flags": [ + "google-translate" + ], + "key": { + "message": "Send" + } + }, + { + "translated": "Адрес сервера с портом", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server Address" + } + }, + { + "translated": "Пожалуйста, введите действительный адрес сервера", + "flags": [ + "google-translate" + ], + "key": { + "message": "Please enter a valid server address" + } + }, + { + "translated": "Ник (псевданим)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Nickname" + } + }, + { + "translated": "Пожалуйста, введите действительный никнейм сервера", + "flags": [ + "google-translate" + ], + "key": { + "message": "Please enter a valid server nickname" + } + }, + { + "translated": "Профиль подключения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Connect Profile" + } + }, + { + "translated": "Выбранный профиль недействителен. Выберите другой или исправьте профиль.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Selected profile is invalid. Select another one or fix the profile." + } + }, + { + "translated": "Управление профилями", + "flags": [ + "google-translate" + ], + "key": { + "message": "Manage profiles" + } + }, + { + "translated": "Название:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Name:" + } + }, + { + "translated": "Тема:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Topic:" + } + }, + { + "translated": "Описание:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Description:" + } + }, + { + "translated": "Пароль:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Password:" + } + }, + { + "translated": "стандарт", + "flags": [ + "google-translate" + ], + "key": { + "message": "Standard" + } + }, + { + "translated": "Тип канала", + "flags": [ + "google-translate" + ], + "key": { + "message": "Channel Type" + } + }, + { + "translated": "временный", + "flags": [ + "google-translate" + ], + "key": { + "message": "Temporary" + } + }, + { + "translated": "Полупостоянный", + "flags": [ + "google-translate" + ], + "key": { + "message": "Semi-Permanent" + } + }, + { + "translated": "перманентный", + "flags": [ + "google-translate" + ], + "key": { + "message": "Permanent" + } + }, + { + "translated": "Канал по умолчанию", + "flags": [ + "google-translate" + ], + "key": { + "message": "Default Channel" + } + }, + { + "translated": "Сортировать этот канал после:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Sort this channel after:" + } + }, + { + "translated": "Необходимая сила разговора:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Needed Talk Power:" + } + }, + { + "translated": "аудио", + "flags": [ + "google-translate" + ], + "key": { + "message": "Audio" + } + }, + { + "translated": "Пресеты", + "flags": [ + "google-translate" + ], + "key": { + "message": "Presets" + } + }, + { + "translated": "Голос Мобайл", + "flags": [ + "google-translate" + ], + "key": { + "message": "Voice Mobile" + } + }, + { + "translated": "Voice Desktop", + "flags": [ + "google-translate" + ], + "key": { + "message": "Voice Desktop" + } + }, + { + "translated": "Музыка", + "flags": [ + "google-translate" + ], + "key": { + "message": "Music" + } + }, + { + "translated": "изготовленный на заказ", + "flags": [ + "google-translate" + ], + "key": { + "message": "Custom" + } + }, + { + "translated": "Пользовательские настройки", + "flags": [ + "google-translate" + ], + "key": { + "message": "Custom Settings" + } + }, + { + "translated": "Speex Ultra-Wideband", + "flags": [ + "google-translate" + ], + "key": { + "message": "Speex Ultra-Wideband" + } + }, + { + "translated": "Качественный:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Quality:" + } + }, + { + "translated": "Обычные необходимые полномочия:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Regular needed powers:" + } + }, + { + "translated": "Присоединиться:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Join:" + } + }, + { + "translated": "Требуемая мощность, чтобы присоединиться к этому каналу", + "flags": [ + "google-translate" + ], + "key": { + "message": "Required power to join this channel" + } + }, + { + "translated": "Посмотреть:", + "flags": [ + "google-translate" + ], + "key": { + "message": "View:" + } + }, + { + "translated": "Требуемая мощность, чтобы увидеть этот канал", + "flags": [ + "google-translate" + ], + "key": { + "message": "Required power to see this channel" + } + }, + { + "translated": "Подписывайся:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Subscribe:" + } + }, + { + "translated": "Требуемая мощность для подписки на этот канал", + "flags": [ + "google-translate" + ], + "key": { + "message": "Required power to subscribe to this channel" + } + }, + { + "translated": "Изменить:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Modify:" + } + }, + { + "translated": "Требуемая мощность для изменения разрешений этого канала", + "flags": [ + "google-translate" + ], + "key": { + "message": "Required power to modify this channel permissions" + } + }, + { + "translated": "Удалять:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Delete:" + } + }, + { + "translated": "Требуемая мощность для удаления этого канала", + "flags": [ + "google-translate" + ], + "key": { + "message": "Required power to delete this channel" + } + }, + { + "translated": "Для передачи файлов необходимы полномочия:", + "flags": [ + "google-translate" + ], + "key": { + "message": "File transfer needed powers:" + } + }, + { + "translated": "Просматривать:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Browse:" + } + }, + { + "translated": "Требуемая мощность для просмотра всех файлов и каталогов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Required power to browse all files and directories" + } + }, + { + "translated": "Загрузить:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Upload:" + } + }, + { + "translated": "Требуемая мощность для загрузки файлов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Required power to upload files" + } + }, + { + "translated": "Скачать:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Download:" + } + }, + { + "translated": "Требуемая мощность для загрузки файлов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Required power to download files" + } + }, + { + "translated": "Переименовать:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Rename:" + } + }, + { + "translated": "Требуемая мощность для переименования файлов в этом канале", + "flags": [ + "google-translate" + ], + "key": { + "message": "Required power to rename files within this channel" + } + }, + { + "translated": "Каталог создания:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Directory create:" + } + }, + { + "translated": "Требуемая мощность для создания каталога", + "flags": [ + "google-translate" + ], + "key": { + "message": "Required power to create a directory" + } + }, + { + "translated": "Требуемая мощность для удаления каталога или файла", + "flags": [ + "google-translate" + ], + "key": { + "message": "Required power to delete a directory or file" + } + }, + { + "translated": "продвинутый", + "flags": [ + "google-translate" + ], + "key": { + "message": "Advanced" + } + }, + { + "translated": "Другие настройки", + "flags": [ + "google-translate" + ], + "key": { + "message": "Other Settings" + } + }, + { + "translated": "Фонетическое название:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Phonetic Name:" + } + }, + { + "translated": "Удалить задержку:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Delete delay:" + } + }, + { + "translated": "Шифровать голосовые данные", + "flags": [ + "google-translate" + ], + "key": { + "message": "Encrypt voice data" + } + }, + { + "translated": "Макс пользователей", + "flags": [ + "google-translate" + ], + "key": { + "message": "Max users" + } + }, + { + "translated": "неограниченный", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unlimited" + } + }, + { + "translated": "Ограничено", + "flags": [ + "google-translate" + ], + "key": { + "message": "Limited" + } + }, + { + "translated": "Пользователи Family Max", + "flags": [ + "google-translate" + ], + "key": { + "message": "Family Max users" + } + }, + { + "translated": "унаследованный", + "flags": [ + "google-translate" + ], + "key": { + "message": "Inherited" + } + }, + { + "translated": "проверено", + "flags": [ + "google-translate" + ], + "key": { + "message": "checked" + } + }, + { + "translated": "Диспетчер виртуального сервера", + "flags": [ + "google-translate" + ], + "key": { + "message": "Manager the Virtual Server" + } + }, + { + "translated": "Максимум клиентов:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Maximum Clients:" + } + }, + { + "translated": "Зарезервированные слоты:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Reserved slots:" + } + }, + { + "translated": "Приветственное сообщение:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Welcome Message:" + } + }, + { + "translated": "хозяин", + "flags": [ + "google-translate" + ], + "key": { + "message": "Host" + } + }, + { + "translated": "переплет", + "flags": [ + "google-translate" + ], + "key": { + "message": "Binding" + } + }, + { + "translated": "Ведущий:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Host:" + } + }, + { + "translated": "Порт:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Port:" + } + }, + { + "translated": "Примечание. Для вступления в силу этих настроек требуется перезагрузка виртуального сервера!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Note: These settings require a virtual server restart to take effect!" + } + }, + { + "translated": "Сообщение хоста", + "flags": [ + "google-translate" + ], + "key": { + "message": "Host message" + } + }, + { + "translated": "Сообщение:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Message:" + } + }, + { + "translated": "Режим сообщения:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Message Mode:" + } + }, + { + "translated": "Нет сообщений", + "flags": [ + "google-translate" + ], + "key": { + "message": "No message" + } + }, + { + "translated": "Показать сообщение в журнале", + "flags": [ + "google-translate" + ], + "key": { + "message": "Show message in log" + } + }, + { + "translated": "Показать сообщение как модальное", + "flags": [ + "google-translate" + ], + "key": { + "message": "Show message as modal" + } + }, + { + "translated": "Показать сообщение как модальное и отключить клиент", + "flags": [ + "google-translate" + ], + "key": { + "message": "Show message as modal and disconnect the client" + } + }, + { + "translated": "Хост-баннер", + "flags": [ + "google-translate" + ], + "key": { + "message": "Host banner" + } + }, + { + "translated": "Баннер Gfx URL:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Banner Gfx URL:" + } + }, + { + "translated": "Gfx Interval", + "flags": [ + "google-translate" + ], + "key": { + "message": "Gfx Interval" + } + }, + { + "translated": "Изменение размера", + "flags": [ + "google-translate" + ], + "key": { + "message": "Resize" + } + }, + { + "translated": "Не корректировать", + "flags": [ + "google-translate" + ], + "key": { + "message": "Do not adjust" + } + }, + { + "translated": "Отрегулируйте, но игнорируйте соотношение сторон", + "flags": [ + "google-translate" + ], + "key": { + "message": "Adjust but ignore ratio aspect" + } + }, + { + "translated": "Отрегулируйте и сохраняйте соотношение сторон", + "flags": [ + "google-translate" + ], + "key": { + "message": "Adjust and keep ratio aspect" + } + }, + { + "translated": "Хост Кнопка", + "flags": [ + "google-translate" + ], + "key": { + "message": "Host Button" + } + }, + { + "translated": "подсказка:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Tooltip:" + } + }, + { + "translated": "URL значка:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Icon URL:" + } + }, + { + "translated": "переводы", + "flags": [ + "google-translate" + ], + "key": { + "message": "Transfers" + } + }, + { + "translated": "Загрузить", + "flags": [ + "google-translate" + ], + "key": { + "message": "Upload" + } + }, + { + "translated": "Ограничение пропускной способности (байт / с):", + "flags": [ + "google-translate" + ], + "key": { + "message": "Bandwidth Limit (Bytes/s):" + } + }, + { + "translated": "Загрузить квоту (МиБ):", + "flags": [ + "google-translate" + ], + "key": { + "message": "Upload Quota (MiB):" + } + }, + { + "translated": "Скачать", + "flags": [ + "google-translate" + ], + "key": { + "message": "Download" + } + }, + { + "translated": "Ограничение пропускной способности:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Bandwidth Limit:" + } + }, + { + "translated": "Скачать квоту:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Download Quota:" + } + }, + { + "translated": "Anti-Flood", + "flags": [ + "google-translate" + ], + "key": { + "message": "Anti-Flood" + } + }, + { + "translated": "Уменьшенные очки за тик:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Reduced points per tick:" + } + }, + { + "translated": "Очки, необходимые для блокировки команд:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Points needed to block commands:" + } + }, + { + "translated": "Очки, необходимые для блокировки IP:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Points needed to block IP:" + } + }, + { + "translated": "Безопасность", + "flags": [ + "google-translate" + ], + "key": { + "message": "Security" + } + }, + { + "translated": "Необходимый уровень безопасности:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Needed Security Level:" + } + }, + { + "translated": "Канал голосового шифрования данных:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Channel voice data encryption:" + } + }, + { + "translated": "Редактировать для каждого канала индивидуально", + "flags": [ + "google-translate" + ], + "key": { + "message": "Edit per channel individually" + } + }, + { + "translated": "Глобально отключен", + "flags": [ + "google-translate" + ], + "key": { + "message": "Globally disabled" + } + }, + { + "translated": "Глобально включен (рекомендуется)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Globally enabled (recommended)" + } + }, + { + "translated": "Разное", + "flags": [ + "google-translate" + ], + "key": { + "message": "Misc" + } + }, + { + "translated": "Группы по умолчанию", + "flags": [ + "google-translate" + ], + "key": { + "message": "Default groups" + } + }, + { + "translated": "Группа серверов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server group" + } + }, + { + "translated": "Музыкальная группа ботов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Music bot group" + } + }, + { + "translated": "Группа каналов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Channel group" + } + }, + { + "translated": "Канал Администратор группы", + "flags": [ + "google-translate" + ], + "key": { + "message": "Channel Admin group" + } + }, + { + "translated": "Пожаловаться", + "flags": [ + "google-translate" + ], + "key": { + "message": "Complain" + } + }, + { + "translated": "Автобан Граф", + "flags": [ + "google-translate" + ], + "key": { + "message": "Autoban Count" + } + }, + { + "translated": "Автобан Время", + "flags": [ + "google-translate" + ], + "key": { + "message": "Autoban Time" + } + }, + { + "translated": "Удалить время", + "flags": [ + "google-translate" + ], + "key": { + "message": "Remove Time" + } + }, + { + "translated": "Минимальное количество клиентов - канал перед тишиной:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Minimum clients is channel before silence:" + } + }, + { + "translated": "Модификатор приоритета динамиков:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Priority speaker dim modificator:" + } + }, + { + "translated": "Удалить задержку для временных каналов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Delete delay for temporary channels" + } + }, + { + "translated": "Список серверов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server list" + } + }, + { + "translated": "Включить отчетность в список серверов TeamSpeak", + "flags": [ + "google-translate" + ], + "key": { + "message": "Enable reporting to the TeamSpeak server list" + } + }, + { + "translated": " Включить отчеты в список серверов TeaSpeak (TeaSpeak не поддерживает этот параметр)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Enable reporting to the TeaSpeak server list (TeaSpeak does not support this setting)" + } + }, + { + "translated": "Сообщения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Messages" + } + }, + { + "translated": "канал", + "flags": [ + "google-translate" + ], + "key": { + "message": "Channel" + } + }, + { + "translated": "Тема по умолчанию:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Default Topic:" + } + }, + { + "translated": "Описание по умолчанию", + "flags": [ + "google-translate" + ], + "key": { + "message": "Default Description" + } + }, + { + "translated": "клиент", + "flags": [ + "google-translate" + ], + "key": { + "message": "Client" + } + }, + { + "translated": "Сохранить", + "flags": [ + "google-translate" + ], + "key": { + "message": "Save" + } + }, + { + "translated": "генеральный", + "flags": [ + "google-translate" + ], + "key": { + "message": "General" + } + }, + { + "translated": "Форум о TeaSpeak", + "flags": [ + "google-translate" + ], + "key": { + "message": "TeaSpeak Forum Connection" + } + }, + { + "translated": "Вы в настоящее время не связаны с вашей учетной записью форума TeaSpeak", + "flags": [ + "google-translate" + ], + "key": { + "message": "You're currently not connected with your TeaSpeak Forum account" + } + }, + { + "translated": "авторизоваться", + "flags": [ + "google-translate" + ], + "key": { + "message": "login" + } + }, + { + "translated": "Вы подключены через форум TeaSpeak", + "flags": [ + "google-translate" + ], + "key": { + "message": "You're connected via TeaSpeak forum" + } + }, + { + "translated": "Имя пользователя:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Username:" + } + }, + { + "translated": "Premium:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Premium:" + } + }, + { + "translated": "Синхронное:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Synchronized:" + } + }, + { + "translated": "включить синхронизацию", + "flags": [ + "google-translate" + ], + "key": { + "message": "enable synchronization" + } + }, + { + "translated": "Микрофон", + "flags": [ + "google-translate" + ], + "key": { + "message": "Microphone" + } + }, + { + "translated": "Прибор:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Device:" + } + }, + { + "translated": "Обнаружение голосовой активности", + "flags": [ + "google-translate" + ], + "key": { + "message": "Voice Activity Detection" + } + }, + { + "translated": "Всегда активный", + "flags": [ + "google-translate" + ], + "key": { + "message": "Always active" + } + }, + { + "translated": "Обнаружение голосовой активности", + "flags": [ + "google-translate" + ], + "key": { + "message": "Voice activity detection" + } + }, + { + "translated": "Нажми чтобы говорить", + "flags": [ + "google-translate" + ], + "key": { + "message": "Push to talk" + } + }, + { + "translated": "Там нет записей настройки для всегда онлайн обнаружения голоса.", + "flags": [ + "google-translate" + ], + "key": { + "message": "There are no setting entries for an always online voice detection." + } + }, + { + "translated": "Выберите клавишу для активации голоса:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Push to talk key:" + } + }, + { + "translated": "неиницализированные", + "flags": [ + "google-translate" + ], + "key": { + "message": "Uninitialised" + } + }, + { + "translated": "Задержка отпускания ключа:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Key release delay:" + } + }, + { + "translated": "Порог голосовой активности ( 20 %)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Voice activity threshold (20%)" + } + }, + { + "translated": "Голос был отключен", + "flags": [ + "google-translate" + ], + "key": { + "message": "Voice had been disabled" + } + }, + { + "translated": "Оратор", + "flags": [ + "google-translate" + ], + "key": { + "message": "Speaker" + } + }, + { + "translated": "Настройки звука", + "flags": [ + "google-translate" + ], + "key": { + "message": "Sound Settings" + } + }, + { + "translated": "Перекрываются одинаковые звуки:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Overlap same sounds:" + } + }, + { + "translated": "Отключите все системные звуки, когда вы отключите звук на выходе.
Если эта опция не отключена, вы все равно будете получать системные звуки типа «пользователь присоединился к вашему каналу».", + "flags": [ + "google-translate" + ], + "key": { + "message": "Mute all system sounds, when you've muted your output.
If this option isn't disabled you'll still receive system sounds like 'user joined your channel'." + } + }, + { + "translated": "название", + "flags": [ + "google-translate" + ], + "key": { + "message": "Name" + } + }, + { + "translated": "активированная", + "flags": [ + "google-translate" + ], + "key": { + "message": "Activated" + } + }, + { + "translated": "Фильтр", + "flags": [ + "google-translate" + ], + "key": { + "message": "Filter" + } + }, + { + "translated": "Переводы", + "flags": [ + "google-translate" + ], + "key": { + "message": "Translations" + } + }, + { + "translated": "Доступные переводы", + "flags": [ + "google-translate" + ], + "key": { + "message": "Available translations" + } + }, + { + "translated": "Английский (по умолчанию / резервный)", + "flags": [ + "google-translate" + ], + "key": { + "message": "English (Default / Fallback)" + } + }, + { + "translated": "Добавить репозиторий", + "flags": [ + "google-translate" + ], + "key": { + "message": "Add repository" + } + }, + { + "translated": "Внимание: на эти настройки влияют только после перезагрузки или перезагрузки!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Attention: These settings get only affected after a restart or reload!" + } + }, + { + "translated": "перезагрузить сейчас", + "flags": [ + "google-translate" + ], + "key": { + "message": "reload now" + } + }, + { + "translated": "профили", + "flags": [ + "google-translate" + ], + "key": { + "message": "Profiles" + } + }, + { + "translated": "Доступные профили", + "flags": [ + "google-translate" + ], + "key": { + "message": "Available profiles" + } + }, + { + "translated": "Сделать выбранным по умолчанию", + "flags": [ + "google-translate" + ], + "key": { + "message": "Set selected as default" + } + }, + { + "translated": "Удалить выбранное", + "flags": [ + "google-translate" + ], + "key": { + "message": "Delete selected" + } + }, + { + "translated": "Создать профиль", + "flags": [ + "google-translate" + ], + "key": { + "message": "Create profile" + } + }, + { + "translated": "Настройки профиля", + "flags": [ + "google-translate" + ], + "key": { + "message": "Profile settings" + } + }, + { + "translated": "Имя профиля:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Profile name:" + } + }, + { + "translated": "Псевдоним по умолчанию:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Default nickname:" + } + }, + { + "translated": "Пароль сервера по умолчанию", + "flags": [ + "google-translate" + ], + "key": { + "message": "Default server password" + } + }, + { + "translated": "Определить тип:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Identify Type:" + } + }, + { + "translated": "Форум Аккаунт", + "flags": [ + "google-translate" + ], + "key": { + "message": "Forum Account" + } + }, + { + "translated": "TeamSpeak", + "flags": [ + "google-translate" + ], + "key": { + "message": "TeamSpeak" + } + }, + { + "translated": "Псевдоним (только в целях отладки!)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Nickname (Debug purposes only!)" + } + }, + { + "translated": "Пожалуйста, введите экспортированную строку TS3 Identity ниже или выберите свою экспортированную идентификацию", + "flags": [ + "google-translate" + ], + "key": { + "message": "Please enter your exported TS3 Identity string bellow or select your exported Identity" + } + }, + { + "translated": "Уникальный идентификатор:", + "flags": [ + "google-translate" + ], + "key": { + "message": "UniqueID:" + } + }, + { + "translated": "Уровень:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Level:" + } + }, + { + "translated": "улучшать", + "flags": [ + "google-translate" + ], + "key": { + "message": "Improve" + } + }, + { + "translated": "Вы не создали / импортировали личность.
Создать новый или импортировать один.", + "flags": [ + "google-translate" + ], + "key": { + "message": "You have'nt generated/imported an identity.
Generate a new one or import one." + } + }, + { + "translated": "Создать новый", + "flags": [ + "google-translate" + ], + "key": { + "message": "Generate new" + } + }, + { + "translated": "Экспорт идентификатора", + "flags": [ + "google-translate" + ], + "key": { + "message": "Export identity" + } + }, + { + "translated": "Вы используете свою учетную запись форума в качестве подтверждения", + "flags": [ + "google-translate" + ], + "key": { + "message": "You're using your forum account as verification" + } + }, + { + "translated": "Это только для отладки и использует имя как уникальный идентификатор", + "flags": [ + "google-translate" + ], + "key": { + "message": "This is just for debug and uses the name as unique identifier" + } + }, + { + "translated": "имя пользователя", + "flags": [ + "google-translate" + ], + "key": { + "message": "Username" + } + }, + { + "translated": "Не удалось импортировать личность (моя маленькая ошибка)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Failed to import identity (my little error)" + } + }, + { + "translated": "Импортировать личность из TeamSpeak или экспортированный", + "flags": [ + "google-translate" + ], + "key": { + "message": "Import an identity from TeamSpeak or an exported identity" + } + }, + { + "translated": "Загрузить из текста", + "flags": [ + "google-translate" + ], + "key": { + "message": "Load from text" + } + }, + { + "translated": "Загрузить из файла", + "flags": [ + "google-translate" + ], + "key": { + "message": "Load from file" + } + }, + { + "translated": "Идентификационные данные успешно загружены. Готов импортировать", + "flags": [ + "google-translate" + ], + "key": { + "message": "Identity successfully loaded. Ready to import" + } + }, + { + "translated": "Импортировать", + "flags": [ + "google-translate" + ], + "key": { + "message": "Import" + } + }, + { + "translated": "Идентичность:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Identity:" + } + }, + { + "translated": "Текущий уровень:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Current level: " + } + }, + { + "translated": "Целевой уровень:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Target level: " + } + }, + { + "translated": "Значение ноль означает неограниченное. Улучшение, пока вы не прервете", + "flags": [ + "google-translate" + ], + "key": { + "message": "A value of zero means unlimited. Improve until you abort" + } + }, + { + "translated": "Потоки:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Threads: " + } + }, + { + "translated": "Количество потоков, используемых для улучшения вашей личности
Оптимальное значение равно количеству ядерных нитей.", + "flags": [ + "google-translate" + ], + "key": { + "message": "The number of threads used to improve your identity
The optimal value is equal to the amount of kernal threads." + } + }, + { + "translated": "Хэш / второй:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Hashes/second:" + } + }, + { + "translated": "Пройденное время:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Elapsed time:" + } + }, + { + "translated": "Профиль действителен", + "flags": [ + "google-translate" + ], + "key": { + "message": "Profile is valid" + } + }, + { + "translated": "Профиль недействителен", + "flags": [ + "google-translate" + ], + "key": { + "message": "Profile is invalid" + } + }, + { + "translated": "Группы каналов", + "flags": [ + "google-translate" + ], + "key": { + "message": "Channel Groups" + } + }, + { + "translated": "Разрешения канала", + "flags": [ + "google-translate" + ], + "key": { + "message": "Channel permissions" + } + }, + { + "translated": "Клиентские разрешения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Client permissions" + } + }, + { + "translated": "Уникальный идентификатор клиента", + "flags": [ + "google-translate" + ], + "key": { + "message": "Client unique ID" + } + }, + { + "translated": "Неверный уникальный идентификатор", + "flags": [ + "google-translate" + ], + "key": { + "message": "Invalid unique id" + } + }, + { + "translated": "Ник:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Nickname:" + } + }, + { + "translated": "Уникальный идентификатор:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unique ID:" + } + }, + { + "translated": "Идентификатор клиентской базы данных:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Client database ID:" + } + }, + { + "translated": "Клиентские разрешения канала", + "flags": [ + "google-translate" + ], + "key": { + "message": "Client channel permissions" + } + }, + { + "translated": "Смена групп", + "flags": [ + "google-translate" + ], + "key": { + "message": "Changing groups of" + } + }, + { + "translated": "разрешения фильтра", + "flags": [ + "google-translate" + ], + "key": { + "message": "filter permissions" + } + }, + { + "translated": "Фильтровать разрешения по имени разрешения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Filter permissions by permission name" + } + }, + { + "translated": "Показать только предоставленные разрешения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Show granted permissions only" + } + }, + { + "translated": "Название разрешения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Permission Name" + } + }, + { + "translated": "Значение", + "flags": [ + "google-translate" + ], + "key": { + "message": "Value" + } + }, + { + "translated": "Пропускать", + "flags": [ + "google-translate" + ], + "key": { + "message": "Skip" + } + }, + { + "translated": "сводить на нет", + "flags": [ + "google-translate" + ], + "key": { + "message": "Negate" + } + }, + { + "translated": "Предоставляется", + "flags": [ + "google-translate" + ], + "key": { + "message": "Granted" + } + }, + { + "translated": "У вас нет разрешения на просмотр этих разрешений", + "flags": [ + "google-translate" + ], + "key": { + "message": "You dont have the permission to view this permissions" + } + }, + { + "translated": "Обновить", + "flags": [ + "google-translate" + ], + "key": { + "message": "Update" + } + }, + { + "translated": "добавлять", + "flags": [ + "google-translate" + ], + "key": { + "message": "Add" + } + }, + { + "translated": "Удалить", + "flags": [ + "google-translate" + ], + "key": { + "message": "Remove" + } + }, + { + "translated": "фильтр", + "flags": [ + "google-translate" + ], + "key": { + "message": "filter" + } + }, + { + "translated": "Имя / IP / UID / HWID", + "flags": [ + "google-translate" + ], + "key": { + "message": "Name/IP/UID/HWID" + } + }, + { + "translated": "причина", + "flags": [ + "google-translate" + ], + "key": { + "message": "Reason" + } + }, + { + "translated": "творец", + "flags": [ + "google-translate" + ], + "key": { + "message": "Creator" + } + }, + { + "translated": "Создано / Истекает", + "flags": [ + "google-translate" + ], + "key": { + "message": "Created / Expires" + } + }, + { + "translated": "перезагружать", + "flags": [ + "google-translate" + ], + "key": { + "message": "Reload" + } + }, + { + "translated": "Показывать только собственные запреты", + "flags": [ + "google-translate" + ], + "key": { + "message": "Show only own bans" + } + }, + { + "translated": "Выделите собственные запреты", + "flags": [ + "google-translate" + ], + "key": { + "message": "Highlight own bans" + } + }, + { + "translated": "Создано:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Created:" + } + }, + { + "translated": "Удалено:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Expire:" + } + }, + { + "translated": "Причина:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Reason:" + } + }, + { + "translated": "Продолжительность:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Duration:" + } + }, + { + "translated": "постоянный", + "flags": [ + "google-translate" + ], + "key": { + "message": "permanent" + } + }, + { + "translated": "Забанить клиента", + "flags": [ + "google-translate" + ], + "key": { + "message": "Ban client by" + } + }, + { + "translated": "Забанить клиента по его текущему нику.
Текущий никнейм нельзя использовать, пока не закончится бан", + "flags": [ + "google-translate" + ], + "key": { + "message": "Bans the client by his current nickname.
The currently nickname cant be used until the ban expired" + } + }, + { + "translated": "ID оборудования", + "flags": [ + "google-translate" + ], + "key": { + "message": "Hardware ID" + } + }, + { + "translated": "Забанить клиента по его идентификатору оборудования.
Аппаратный идентификатор имеет разные значения, зависит от агента пользователя
TeaClient: аппаратный идентификатор будет равен MAC-адресу
TeaWeb: веб-клиент TeaSpeak не имеет аппаратного идентификатора, он будет случайным
Клиент TeamSpeak 3: идентификатор оборудования будет результатом некоторых хэшей из определенных аппаратных свойств", + "flags": [ + "google-translate" + ], + "key": { + "message": "Bans the client by his hardware id.
The hardware id has different meanings, depends on the users agent
TeaClient: The hardware id will be equal to the mac address
TeaWeb: The TeaSpeak web client hasn't a hardware id, it will be random
TeamSpeak 3 client: The hardware id will be a result of some hashes from hardware specific properties" + } + }, + { + "translated": "Айпи адрес", + "flags": [ + "google-translate" + ], + "key": { + "message": "IP Address" + } + }, + { + "translated": "IP:", + "flags": [ + "google-translate" + ], + "key": { + "message": "IP:" + } + }, + { + "translated": "Интерпретировать IP / имя как:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Interpret IP/Name as:" + } + }, + { + "translated": "Подстановочный знак IPv4", + "flags": [ + "google-translate" + ], + "key": { + "message": "Wildcard IPv4" + } + }, + { + "translated": "Подстановочный знак IPv6", + "flags": [ + "google-translate" + ], + "key": { + "message": "Wildcard IPv6" + } + }, + { + "translated": "Фиксированная строка", + "flags": [ + "google-translate" + ], + "key": { + "message": "Fixed string" + } + }, + { + "translated": "Регулярное выражение", + "flags": [ + "google-translate" + ], + "key": { + "message": "Regular Expression" + } + }, + { + "translated": "ID оборудования:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Hardware ID:" + } + }, + { + "translated": "Использовать этот бан как глобальный бан", + "flags": [ + "google-translate" + ], + "key": { + "message": "Use this ban as a global ban" + } + }, + { + "translated": "Глобальные запреты - это запреты, которые распространяются на весь экземпляр.
Это означает, что (если это правило применимо к жертве) не может присоединиться к любому виртуальному серверу!
Глобальные запреты по умолчанию показываются каждой группе администраторов сервера,
но может быть создан только с правами запроса", + "flags": [ + "google-translate" + ], + "key": { + "message": "Global bans are bans which apply instance wide.
This means that (if this rule apply to a victim) cant join any virtual server!
Global bans are by default shown to every server admin group,
but could only be created with query rights" + } + }, + { + "translated": "Не играет музыку", + "flags": [ + "google-translate" + ], + "key": { + "message": "Not playing any music" + } + }, + { + "translated": "Версия:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Version:" + } + }, + { + "translated": "Информация о браузере", + "flags": [ + "google-translate" + ], + "key": { + "message": "Browser info" + } + }, + { + "translated": "Онлайн с:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Online since:" + } + }, + { + "translated": "Объем:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Volume:" + } + }, + { + "translated": "Учетная запись TeaSpeak:", + "flags": [ + "google-translate" + ], + "key": { + "message": "TeaSpeak Account:" + } + }, + { + "translated": "Группы серверов:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server groups:" + } + }, + { + "translated": "Группа каналов:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Channel group:" + } + }, + { + "translated": "Далеко", + "flags": [ + "google-translate" + ], + "key": { + "message": "Away" + } + }, + { + "translated": "Динамики / наушники отключены", + "flags": [ + "google-translate" + ], + "key": { + "message": "Speakers/Headphones disabled" + } + }, + { + "translated": "Микрофон отключен", + "flags": [ + "google-translate" + ], + "key": { + "message": "Microphone disabled" + } + }, + { + "translated": "Динамики / наушники отключены", + "flags": [ + "google-translate" + ], + "key": { + "message": "Speakers/Headphones Muted" + } + }, + { + "translated": "Микрофон отключен", + "flags": [ + "google-translate" + ], + "key": { + "message": "Microphone Muted" + } + }, + { + "translated": "Livetime:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Livetime:" + } + }, + { + "translated": "Онлайн с", + "flags": [ + "google-translate" + ], + "key": { + "message": "Online since" + } + }, + { + "translated": "Запуск сервера", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server start" + } + }, + { + "translated": "Удаленный объем:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Remote Volume:" + } + }, + { + "translated": "Локальный том:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Local Volume:" + } + }, + { + "translated": "В настоящее время воспроизводится:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Currently replaying:" + } + }, + { + "translated": "Адрес:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Address:" + } + }, + { + "translated": "Тип:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Type:" + } + }, + { + "translated": "Uptime:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Uptime:" + } + }, + { + "translated": "Текущие каналы:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Current Channels:" + } + }, + { + "translated": "Текущие клиенты:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Current Clients:" + } + }, + { + "translated": "Текущие запросы:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Current Queries:" + } + }, + { + "translated": "Обновить информацию", + "flags": [ + "google-translate" + ], + "key": { + "message": "Update info" + } + }, + { + "translated": "кодек:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Codec:" + } + }, + { + "translated": "Качество кодека:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Codec Quality:" + } + }, + { + "translated": "Текущие клиенты:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Current clients:" + } + }, + { + "translated": "Статус подписки:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Subscription Status:" + } + }, + { + "translated": "подписной", + "flags": [ + "google-translate" + ], + "key": { + "message": "Subscribed" + } + }, + { + "translated": "Отписался", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unsubscribed" + } + }, + { + "translated": "Шифрование голосовых данных:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Voice Data Encryption:" + } + }, + { + "translated": "Незашифрованная", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unencrypted" + } + }, + { + "translated": "Зашифрованные", + "flags": [ + "google-translate" + ], + "key": { + "message": "Encrypted" + } + }, + { + "translated": "(Перезаписывается сервером с помощью Encrypted)", + "flags": [ + "google-translate" + ], + "key": { + "message": "(Overridden by the server with Encrypted)" + } + }, + { + "translated": "(Перезаписывается сервером с незашифрованным)", + "flags": [ + "google-translate" + ], + "key": { + "message": "(Overridden by the server with Unencrypted)" + } + }, + { + "translated": "Описание", + "flags": [ + "google-translate" + ], + "key": { + "message": "Description" + } + }, + { + "translated": "Ты ткнул", + "flags": [ + "google-translate" + ], + "key": { + "message": "You have been poked by" + } + }, + { + "translated": "Установите имя для входа в свою учетную запись Server Query.", + "flags": [ + "google-translate" + ], + "key": { + "message": "Set the login name for your Server Query account." + } + }, + { + "translated": "Вы получите свой пароль на следующем шаге.", + "flags": [ + "google-translate" + ], + "key": { + "message": "You'll receive your password within the next step." + } + }, + { + "translated": "Создайте", + "flags": [ + "google-translate" + ], + "key": { + "message": "Create" + } + }, + { + "translated": "Учетные данные вашего сервера:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Your server query credentials:" + } + }, + { + "translated": "Скопировать имя пользователя", + "flags": [ + "google-translate" + ], + "key": { + "message": "Copy username" + } + }, + { + "translated": "создать учетную запись", + "flags": [ + "google-translate" + ], + "key": { + "message": "Create account" + } + }, + { + "translated": "Удалить аккаунт", + "flags": [ + "google-translate" + ], + "key": { + "message": "Delete account" + } + }, + { + "translated": "Переименовать аккаунт", + "flags": [ + "google-translate" + ], + "key": { + "message": "Rename account" + } + }, + { + "translated": "Изменить пароль", + "flags": [ + "google-translate" + ], + "key": { + "message": "Change password" + } + }, + { + "translated": "поиск", + "flags": [ + "google-translate" + ], + "key": { + "message": "search" + } + }, + { + "translated": "Уникальный идентификатор", + "flags": [ + "google-translate" + ], + "key": { + "message": "Unique ID" + } + }, + { + "translated": "Ограниченный сервер", + "flags": [ + "google-translate" + ], + "key": { + "message": "Bounded Server" + } + }, + { + "translated": "обновление", + "flags": [ + "google-translate" + ], + "key": { + "message": "Refresh" + } + }, + { + "translated": "Создать плейлист", + "flags": [ + "google-translate" + ], + "key": { + "message": "Create playlist" + } + }, + { + "translated": "Удалить плейлист", + "flags": [ + "google-translate" + ], + "key": { + "message": "Delete playlist" + } + }, + { + "translated": "Я БЫ", + "flags": [ + "google-translate" + ], + "key": { + "message": "ID" + } + }, + { + "translated": "заглавие", + "flags": [ + "google-translate" + ], + "key": { + "message": "Title" + } + }, + { + "translated": "Тип", + "flags": [ + "google-translate" + ], + "key": { + "message": "Type" + } + }, + { + "translated": "Используемый", + "flags": [ + "google-translate" + ], + "key": { + "message": "Used" + } + }, + { + "translated": "Выделите собственные плейлисты", + "flags": [ + "google-translate" + ], + "key": { + "message": "Highlight own playlists" + } + }, + { + "translated": "Плейлист используется ботом", + "flags": [ + "google-translate" + ], + "key": { + "message": "Playlist is in use by bot " + } + }, + { + "translated": "Плейлист не используется", + "flags": [ + "google-translate" + ], + "key": { + "message": "Playlist isnt use" + } + }, + { + "translated": "Владелец плейлиста:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Playlist owner:" + } + }, + { + "translated": "Заглавие:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Title:" + } + }, + { + "translated": "Бот привязан", + "flags": [ + "google-translate" + ], + "key": { + "message": "Bot bound" + } + }, + { + "translated": "Глобальный", + "flags": [ + "google-translate" + ], + "key": { + "message": "Global" + } + }, + { + "translated": "песни", + "flags": [ + "google-translate" + ], + "key": { + "message": "Songs" + } + }, + { + "translated": "Веб-сайт", + "flags": [ + "google-translate" + ], + "key": { + "message": "Url" + } + }, + { + "translated": "нагруженный", + "flags": [ + "google-translate" + ], + "key": { + "message": "loaded" + } + }, + { + "translated": "Добавить песню", + "flags": [ + "google-translate" + ], + "key": { + "message": "Add song" + } + }, + { + "translated": "Настройки воспроизведения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Playback settings" + } + }, + { + "translated": "Режим воспроизведения", + "flags": [ + "google-translate" + ], + "key": { + "message": "Replay mode" + } + }, + { + "translated": "Нормальный", + "flags": [ + "google-translate" + ], + "key": { + "message": "Normal" + } + }, + { + "translated": "Loop list", + "flags": [ + "google-translate" + ], + "key": { + "message": "Loop list" + } + }, + { + "translated": "Петля однократная", + "flags": [ + "google-translate" + ], + "key": { + "message": "Loop single entry" + } + }, + { + "translated": "шарканье", + "flags": [ + "google-translate" + ], + "key": { + "message": "Shuffle" + } + }, + { + "translated": "Удалить проигранную песню:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Delete played song:" + } + }, + { + "translated": "Текущая воспроизводящая песня:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Current replaying song:" + } + }, + { + "translated": "Плейлист завершил статус", + "flags": [ + "google-translate" + ], + "key": { + "message": "Playlist finished status" + } + }, + { + "translated": "Доступ / изменение полномочий", + "flags": [ + "google-translate" + ], + "key": { + "message": "Access/modify powers" + } + }, + { + "translated": "Посмотреть мощность", + "flags": [ + "google-translate" + ], + "key": { + "message": "View power" + } + }, + { + "translated": "изменять", + "flags": [ + "google-translate" + ], + "key": { + "message": "Modify" + } + }, + { + "translated": "Разрешение изменить", + "flags": [ + "google-translate" + ], + "key": { + "message": "Permission modify" + } + }, + { + "translated": "удалять", + "flags": [ + "google-translate" + ], + "key": { + "message": "Delete" + } + }, + { + "translated": "Полномочия по управлению песней", + "flags": [ + "google-translate" + ], + "key": { + "message": "Song management powers" + } + }, + { + "translated": "Песня добавить", + "flags": [ + "google-translate" + ], + "key": { + "message": "Song add" + } + }, + { + "translated": "Повторный заказ песни", + "flags": [ + "google-translate" + ], + "key": { + "message": "Song reorder" + } + }, + { + "translated": "Удалить песню", + "flags": [ + "google-translate" + ], + "key": { + "message": "Song delete" + } + }, + { + "translated": "Идентификатор песни:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Song ID:" + } + }, + { + "translated": "Порядок песен:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Song order:" + } + }, + { + "translated": "Распознаватель URL:", + "flags": [ + "google-translate" + ], + "key": { + "message": "URL resolver:" + } + }, + { + "translated": "Песня загружена:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Song loaded:" + } + }, + { + "translated": "Показать метаданные здесь!", + "flags": [ + "google-translate" + ], + "key": { + "message": "Display metdata here!" + } + }, + { + "translated": "Метаданные:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Metadata:" + } + }, + { + "translated": "Загрузчик URL:", + "flags": [ + "google-translate" + ], + "key": { + "message": "URL loader:" + } + }, + { + "translated": "Добавить сайт", + "flags": [ + "google-translate" + ], + "key": { + "message": "Add URL" + } + }, + { + "translated": "Создать новую закладку / каталог", + "flags": [ + "google-translate" + ], + "key": { + "message": "Create new bookmark/directory" + } + }, + { + "translated": "Удалить выбранную закладку / каталог", + "flags": [ + "google-translate" + ], + "key": { + "message": "Delete selected bookmark/directory" + } + }, + { + "translated": "Настройки закладки", + "flags": [ + "google-translate" + ], + "key": { + "message": "Bookmark settings" + } + }, + { + "translated": "Имя закладки:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Bookmark name:" + } + }, + { + "translated": "Подключить профиль:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Connect profile:" + } + }, + { + "translated": "Свойства сервера", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server Properties" + } + }, + { + "translated": "Адрес сервера:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server address:" + } + }, + { + "translated": "Порт сервера:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Server port:" + } + }, + { + "translated": "Свойства подключения (пока не поддерживается)", + "flags": [ + "google-translate" + ], + "key": { + "message": "Connect Properties (Not yet supported)" + } + }, + { + "translated": "Имя пользователя по умолчанию", + "flags": [ + "google-translate" + ], + "key": { + "message": "Default Username" + } + }, + { + "translated": "Текущий канал", + "flags": [ + "google-translate" + ], + "key": { + "message": "Current Channel" + } + }, + { + "translated": "Пароль канала по умолчанию", + "flags": [ + "google-translate" + ], + "key": { + "message": "Default channel password" + } + }, + { + "translated": "Имя каталога:", + "flags": [ + "google-translate" + ], + "key": { + "message": "Directory name:" + } + } + ] +} \ No newline at end of file diff --git a/shared/js/ui/modal/ModalAvatarList.ts b/shared/js/ui/modal/ModalAvatarList.ts new file mode 100644 index 00000000..e69de29b diff --git a/shared/js/ui/modal/ModalIconSelect.ts b/shared/js/ui/modal/ModalIconSelect.ts new file mode 100644 index 00000000..e69de29b From add76cbd20e9395cec0e598ec471dfc03d6191d0 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Mon, 25 Mar 2019 20:04:04 +0100 Subject: [PATCH 04/13] A lots of updates --- ChangeLog.md | 9 + files.php | 2 +- shared/css/static/context_menu.scss | 252 +++++++++++----------- shared/css/static/control_bar.scss | 7 +- shared/css/static/modal-avatar.scss | 175 +++++++++++++++ shared/css/static/modal-channel.scss | 49 +++++ shared/css/static/modal-connect.scss | 4 +- shared/css/static/modal-icons.scss | 150 +++++++++++++ shared/css/static/modal-server.scss | 47 ++++ shared/html/index.php | 5 +- shared/html/manifest.json | 3 +- shared/html/templates.html | 248 +++++++++++++++++++-- shared/js/FileManager.ts | 145 ++++++++----- shared/js/connection/CommandHelper.ts | 79 +++---- shared/js/contextMenu.ts | 19 +- shared/js/load.ts | 154 ++++++++++--- shared/js/log.ts | 21 +- shared/js/main.ts | 6 + shared/js/permission/PermissionManager.ts | 21 +- shared/js/settings.ts | 5 + shared/js/ui/channel.ts | 13 +- shared/js/ui/client.ts | 13 +- shared/js/ui/frames/ControlBar.ts | 17 +- shared/js/ui/frames/SelectedItemInfo.ts | 16 +- shared/js/ui/modal/ModalAvatarList.ts | 162 ++++++++++++++ shared/js/ui/modal/ModalCreateChannel.ts | 35 ++- shared/js/ui/modal/ModalIconSelect.ts | 144 +++++++++++++ shared/js/ui/modal/ModalServerEdit.ts | 21 +- shared/js/ui/server.ts | 23 +- 29 files changed, 1529 insertions(+), 316 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 67d290ab..ee0a5559 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,13 @@ # Changelog: +* **XX.XX.XX** + - Improved icon and avatar cache handling + - Added an icon manager + - Fixed bookmark create modal style + - Fixed control bar drop downs going over the edge + - Fixed context menu overflowing and going out of the side + - Improved host banner url revoke (only revoke after a new one has been generated) + - Added some fancy console messages + * **17.03.19** - Using VAD by default instead of PPT - Improved mobile experience: diff --git a/files.php b/files.php index dec1bd59..c089d028 100644 --- a/files.php +++ b/files.php @@ -4,7 +4,7 @@ /* shared part */ [ /* shared html and php files */ "type" => "html", - "search-pattern" => "/^([a-zA-Z]+)\.(html|php)$/", + "search-pattern" => "/^([a-zA-Z]+)\.(html|php|json)$/", "build-target" => "dev|rel", "path" => "./", diff --git a/shared/css/static/context_menu.scss b/shared/css/static/context_menu.scss index afa899dc..33ea6153 100644 --- a/shared/css/static/context_menu.scss +++ b/shared/css/static/context_menu.scss @@ -3,140 +3,148 @@ display: none; z-index: 2000; position: absolute; - border: 1px solid #CCC; - white-space: nowrap; - font-family: sans-serif; - background: #FFF; - color: #333; - padding: 3px; - * { - font-family: Arial, serif; - font-size: 12px; - white-space: pre; - line-height: 1; - vertical-align: middle; - } + .context-menu-container { + border: 1px solid #CCC; + white-space: nowrap; + font-family: sans-serif; + background: #FFF; + color: #333; + padding: 3px; - hr { - margin-top: 8px; - margin-bottom: 8px; - } - - .entry { - /*padding: 8px 12px;*/ - padding-right: 12px; - cursor: pointer; - list-style-type: none; - transition: all .3s ease; - user-select: none; - align-items: center; - - display: flex; - - &.disabled { - background-color: lightgray; - cursor: not-allowed; + &.left { + margin-left: -100%; + width: 100%; } - &:hover:not(.disabled) { - background-color: #DEF; + * { + font-family: Arial, serif; + font-size: 12px; + white-space: pre; + line-height: 1; + vertical-align: middle; } - } - .icon_empty, .icon { - margin-right: 4px; - } + hr { + margin-top: 8px; + margin-bottom: 8px; + } - .arrow { - cursor: pointer; - pointer-events: all; - width: 7px; - height: 7px; - padding: 0; - margin-right: 5px; - margin-left: 5px; + .entry { + /*padding: 8px 12px;*/ + padding-right: 12px; + cursor: pointer; + list-style-type: none; + transition: all .3s ease; + user-select: none; + align-items: center; - position: absolute; - right: 3px; - } + display: flex; - .sub-container { - padding-right: 3px; - position: relative; + &.disabled { + background-color: lightgray; + cursor: not-allowed; + } - &:hover { - .sub-menu { + &:hover:not(.disabled) { + background-color: #DEF; + } + } + + .icon_empty, .icon { + margin-right: 4px; + } + + .arrow { + cursor: pointer; + pointer-events: all; + width: 7px; + height: 7px; + padding: 0; + margin-right: 5px; + margin-left: 5px; + + position: absolute; + right: 3px; + } + + .sub-container { + padding-right: 3px; + position: relative; + + &:hover { + .sub-menu { + display: block; + } + } + } + + .sub-menu { + display: none; + left: 100%; + top: -4px; + position: absolute; + margin-left: 3px; + } + + .checkbox { + margin-top: 1px; + margin-left: 1px; + display: block; + position: relative; + padding-left: 14px; + margin-bottom: 12px; + cursor: pointer; + font-size: 22px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + /* Hide the browser's default checkbox */ + input { + position: absolute; + opacity: 0; + cursor: pointer; + display: none; + } + + .checkmark { + position: absolute; + top: 0; + left: 0; + height: 11px; + width: 11px; + background-color: #eee; + + &:after { + content: ""; + position: absolute; + display: none; + + left: 4px; + top: 1px; + width: 3px; + height: 7px; + border: solid white; + border-width: 0 2px 2px 0; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + } + } + + &:hover input ~ .checkmark { + background-color: #ccc; + } + + input:checked ~ .checkmark { + background-color: #2196F3; + } + + input:checked ~ .checkmark:after { display: block; } } } - - .sub-menu { - display: none; - left: 100%; - top: -4px; - position: absolute; - margin-left: 3px; - } - - .checkbox { - margin-top: 1px; - margin-left: 1px; - display: block; - position: relative; - padding-left: 14px; - margin-bottom: 12px; - cursor: pointer; - font-size: 22px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - - /* Hide the browser's default checkbox */ - input { - position: absolute; - opacity: 0; - cursor: pointer; - display: none; - } - - .checkmark { - position: absolute; - top: 0; - left: 0; - height: 11px; - width: 11px; - background-color: #eee; - - &:after { - content: ""; - position: absolute; - display: none; - - left: 4px; - top: 1px; - width: 3px; - height: 7px; - border: solid white; - border-width: 0 2px 2px 0; - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); - transform: rotate(45deg); - } - } - - &:hover input ~ .checkmark { - background-color: #ccc; - } - - input:checked ~ .checkmark { - background-color: #2196F3; - } - - input:checked ~ .checkmark:after { - display: block; - } - } } \ No newline at end of file diff --git a/shared/css/static/control_bar.scss b/shared/css/static/control_bar.scss index 2ec3a444..a8d85a75 100644 --- a/shared/css/static/control_bar.scss +++ b/shared/css/static/control_bar.scss @@ -7,8 +7,7 @@ $background:lightgray; flex-direction: row; /* tmp fix for ultra small devices */ - overflow-x: auto; - overflow-y: hidden; + overflow-y: visible; .divider { border-left:2px solid gray; @@ -46,6 +45,8 @@ $background:lightgray; } .button-dropdown { + position: relative; + .buttons { display: flex; flex-direction: row; @@ -104,7 +105,7 @@ $background:lightgray; /*box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);*/ &.right { - + right: 0; } .icon { diff --git a/shared/css/static/modal-avatar.scss b/shared/css/static/modal-avatar.scss index e69de29b..e7162e34 100644 --- a/shared/css/static/modal-avatar.scss +++ b/shared/css/static/modal-avatar.scss @@ -0,0 +1,175 @@ +.modal-avatar-list { + display: flex; + flex-direction: row; + + .container-list { + width: 50%; + + margin-top: 5px; + + display: flex; + flex-direction: column; + justify-content: stretch; + + .column { + &.column-username { + width: calc(50% - 100px); + overflow: hidden; + text-overflow: ellipsis; + } + + &.column-unique-id { + width: calc(50% - 100px); + overflow: hidden; + text-overflow: ellipsis; + } + + &.column-size { + width: 75px; + flex-grow: 0; + flex-shrink: 0; + + text-align: center; + } + + &.column-timestamp { + width: 150px; + flex-grow: 0; + flex-shrink: 0; + + text-align: center; + } + } + + .list-header { + flex-grow: 0; + flex-shrink: 0; + display: flex; + flex-direction: row; + + .column { + border: 1px solid lightgray; + text-align: center; + } + } + + .list-entries-container { + flex-grow: 1; + display: flex; + flex-direction: column; + justify-content: start; + overflow-y: auto; + min-height: 250px; + + .entry { + display: flex; + flex-direction: row; + + .column { + margin-left: 2px; + } + + cursor: pointer; + + &.selected { + background-color: lightblue; + } + } + + &.scrollbar { + .column-username { + width: calc(50% - 100px + 30px) + } + + .column-unique-id { + width: calc(50% - 100px + 30px) + } + } + } + } + + .container-info { + margin-left: 10px; + + position: relative; + width: 50%; + + .container-data { + width: 100%; + } + + .container-preview { + display: flex; + flex-direction: row; + justify-content: stretch; + + .container-image { + flex-shrink: 0; + flex-grow: 0; + + width: 302px; + height: 302px; + + background-color: whitesmoke; + border: 1px solid black; + border-radius: 2px; + + position: relative; + overflow: hidden; + + > div { + top: 0; + bottom: 0; + left: 0; + right: 0; + + position: absolute; + } + } + + .container-image-data { + margin-left: 10px; + + flex-shrink: 1; + flex-grow: 1; + + a { + text-align: center; + } + + .form-group { + width: 100%; + margin-bottom: 0px; + } + } + } + + .container-buttons { + width: 100%; + margin-top: 20px; + + display: flex; + flex-direction: row; + justify-content: space-between; + } + + .disabled-overlay { + position: absolute; + + top: 0; + bottom: 0; + left: 0; + right: 0; + + background-color: gray; + + display: flex; + flex-direction: column; + justify-content: space-around; + + a { + text-align: center; + } + } + } +} \ No newline at end of file diff --git a/shared/css/static/modal-channel.scss b/shared/css/static/modal-channel.scss index 8c9bae58..c4d875fd 100644 --- a/shared/css/static/modal-channel.scss +++ b/shared/css/static/modal-channel.scss @@ -1,3 +1,52 @@ +.container-channel-edit-general { + .container-name-icon { + display: flex; + flex-direction: row; + justify-content: stretch; + + .container-name { + flex-grow: 1; + flex-shrink: 1; + } + + .container-icon { + flex-grow: 0; + flex-shrink: 0; + } + } + + .container-icon { + width: 30px; + + margin-left: 10px; + + .button-select-icon { + left: 0; + right: 0; + top: 0; + bottom: 0; + + position: absolute; + + .icon-node { + cursor: pointer; + + height: 100%; + width: 100%; + + &:hover { + background-color: #00000011; + } + + > div { + vertical-align: middle; + text-align: center; + } + } + } + } +} + .container-channel-settings-standard { flex-grow: 1; display: flex; diff --git a/shared/css/static/modal-connect.scss b/shared/css/static/modal-connect.scss index ca88fbac..8ab6907e 100644 --- a/shared/css/static/modal-connect.scss +++ b/shared/css/static/modal-connect.scss @@ -43,7 +43,7 @@ .container-password { flex-grow: 0; - flex-shrink: 0; + flex-shrink: 4; margin-left: 15px; } @@ -61,7 +61,7 @@ .container-manage { flex-grow: 0; - flex-shrink: 0; + flex-shrink: 4; margin-left: 15px; } diff --git a/shared/css/static/modal-icons.scss b/shared/css/static/modal-icons.scss index e69de29b..e79a27ea 100644 --- a/shared/css/static/modal-icons.scss +++ b/shared/css/static/modal-icons.scss @@ -0,0 +1,150 @@ +.modal-icon-select { + display: flex; + flex-direction: column; + justify-content: stretch; + + .container-icons { + flex-grow: 1; + flex-shrink: 1; + + display: flex; + flex-direction: row; + justify-content: stretch; + + > div { + width: 50%; + + &:not(:first-of-type) { + margin-left: 10px; + } + } + + .content, .container-icons-list { + flex-grow: 1; + flex-shrink: 1; + + display: flex; + flex-direction: column; + } + + .container-icons-list { + position: relative; + + > div { + border-radius: 3px; + } + + .container-icons-remote, .container-icons-local { + width: 100%; + min-height: 300px; + + overflow-x: hidden; + overflow-y: auto; + + background-color: whitesmoke; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + .icon-container, .icon { + margin-left: 1px; + margin-right: 1px; + } + + &.icon-select { + .icon-container, .icon { + cursor: pointer; + + &:hover { + background-color: #00000011; + border: 1px solid black; + } + + &.selected { + background-color: #00330011; + border: 1px solid red; + } + + &:hover, &.selected { + width: 18px; + height: 18px; + + margin: -1px 0px; + } + } + } + } + + .container-loading, .container-no-permissions, .container-error { + top: 0; + bottom: 0; + left: 0; + right: 0; + + position: absolute; + background-color: grey; + + cursor: not-allowed; + + text-align: center; + display: flex; + flex-direction: column; + justify-content: space-around; + + > a { + padding-bottom: 30px; + } + } + + .container-loading { + z-index: 40; + } + + .container-error { + z-index: 30; + } + .container-no-permissions { + z-index: 20; + } + } + } + + .container-buttons { + margin-top: 20px; + + flex-grow: 0; + flex-shrink: 0; + + display: flex; + flex-direction: row; + justify-content: stretch; + + .spacer { + flex-grow: 1; + flex-shrink: 1; + } + + .btn { + flex-grow: 0; + flex-shrink: 0; + } + + .button-select { + margin-left: 10px; + + display: flex; + align-items: center; + flex-direction: row; + + .selected-item-container { + height: 16px; + vertical-align: sub; + } + } + + .button-select-no-icon { + margin-left: 10px; + } + } +} \ No newline at end of file diff --git a/shared/css/static/modal-server.scss b/shared/css/static/modal-server.scss index 357af6b1..ab4374f0 100644 --- a/shared/css/static/modal-server.scss +++ b/shared/css/static/modal-server.scss @@ -19,6 +19,53 @@ flex-shrink: 70; } } + + .container-name-icon { + display: flex; + flex-direction: row; + justify-content: stretch; + + .container-name { + flex-grow: 1; + flex-shrink: 1; + } + + .container-icon { + flex-grow: 0; + flex-shrink: 0; + } + } + + .container-icon { + width: 30px; + + margin-left: 10px; + + .button-select-icon { + left: 0; + right: 0; + top: 0; + bottom: 0; + + position: absolute; + + .icon-node { + cursor: pointer; + + height: 100%; + width: 100%; + + &:hover { + background-color: #00000011; + } + + > div { + vertical-align: middle; + text-align: center; + } + } + } + } } .container-server-settings-host { padding: 5px; diff --git a/shared/html/index.php b/shared/html/index.php index 93750af9..b0f7c348 100644 --- a/shared/html/index.php +++ b/shared/html/index.php @@ -35,11 +35,12 @@ + - +
-

Ooops, we encountered some trouble while loading important files!

+

Ooops, we encountered some trouble while loading important files!

diff --git a/shared/html/manifest.json b/shared/html/manifest.json index fc0b7a5e..61415110 100644 --- a/shared/html/manifest.json +++ b/shared/html/manifest.json @@ -8,9 +8,8 @@ "sizes": "256x256" } ], - "start_url": "?", + "start_url": "/?", "background_color": "#18BC9C", "display": "standalone", - "scope": "/", "theme_color": "#18BC9C" } \ No newline at end of file diff --git a/shared/html/templates.html b/shared/html/templates.html index d5beca49..3c7f754d 100644 --- a/shared/html/templates.html +++ b/shared/html/templates.html @@ -113,6 +113,14 @@
{{tr "View/edit permissions" /}}
+
+
+ +
+
@@ -127,7 +135,7 @@ -
+
@@ -135,7 +143,7 @@
@@ -317,7 +325,7 @@
{{tr "Selected profile is invalid. Select another one or fix the profile." /}}
- +
@@ -330,11 +338,23 @@ + + + + + + - \ No newline at end of file + +
Loading...
@@ -1312,18 +1300,6 @@
-
@@ -1536,9 +1512,13 @@
{{else type == "default" }} -
{{tr "English (Default / Fallback)" /}}
+
+
+
{{tr "English (Default / Fallback)" /}}
+
{{else}}
+
{{> name}}
From 5cb1b8b4da861ec65ebf383841f987222b7ce5a6 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Thu, 28 Mar 2019 17:29:42 +0100 Subject: [PATCH 07/13] Added flags to translations --- shared/js/i18n/localize.ts | 83 +++++++-------------- shared/js/ui/modal/ModalConnect.ts | 2 +- shared/js/ui/modal/ModalSettings.ts | 108 ++++++++++++++-------------- 3 files changed, 82 insertions(+), 111 deletions(-) diff --git a/shared/js/i18n/localize.ts b/shared/js/i18n/localize.ts index e9231269..c7eb38e8 100644 --- a/shared/js/i18n/localize.ts +++ b/shared/js/i18n/localize.ts @@ -39,21 +39,20 @@ namespace i18n { email: string; } - export interface FileInfo { - name: string; - contributors: Contributor[]; - } - export interface TranslationFile { - url: string; + path: string; + full_url: string; - info: FileInfo; translations: Translation[]; } export interface RepositoryTranslation { key: string; path: string; + + country_code: string; + name: string; + contributors: Contributor[]; } export interface TranslationRepository { @@ -85,7 +84,7 @@ namespace i18n { return translated; } - async function load_translation_file(url: string) : Promise { + async function load_translation_file(url: string, path: string) : Promise { return new Promise((resolve, reject) => { $.ajax({ url: url, @@ -98,7 +97,8 @@ namespace i18n { return; } - file.url = url; + file.full_url = url; + file.path = path; //TODO validate file resolve(file); } catch(error) { @@ -113,8 +113,8 @@ namespace i18n { }); } - export function load_file(url: string) : Promise { - return load_translation_file(url).then(result => { + export function load_file(url: string, path: string) : Promise { + return load_translation_file(url, path).then(result => { log.info(LogCategory.I18N, tr("Successfully initialized up translation file from %s"), url); translations = result.translations; return Promise.resolve(); @@ -169,6 +169,7 @@ namespace i18n { current_language?: string; current_translation_url: string; + current_translation_path: string; } export interface RepositoryConfig { @@ -248,65 +249,31 @@ namespace i18n { config.save_repository_config(); } - export function iterate_translations(callback_entry: (repository: TranslationRepository, entry: TranslationFile) => any, callback_finish: () => any) { - let count = 0; - const update_finish = () => { - if(count == 0 && callback_finish) - callback_finish(); - }; + export async function iterate_repositories(callback_entry: (repository: TranslationRepository) => any) { + const promises = []; - for(const repo of registered_repositories()) { - count++; - load_repository0(repo, false).then(() => { - for(const translation of repo.translations || []) { - const translation_path = repo.url + "/" + translation.path; - count++; - - load_translation_file(translation_path).then(file => { - if(callback_entry) { - try { - callback_entry(repo, file); - } catch (error) { - console.error(error); - //TODO more error handling? - } - } - - count--; - update_finish(); - }).catch(error => { - log.warn(LogCategory.I18N, tr("Failed to load translation file for repository %s. Translation: %s (%s) Error: %o"), repo.name, translation.key, translation_path, error); - - count--; - update_finish(); - }); - } - - count--; - update_finish(); - }).catch(error => { - log.warn(LogCategory.I18N, tr("Failed to load repository while iteration: %s (%s). Error: %o"), (repo || {name: "unknown"}).name, (repo || {url: "unknown"}).url, error); - - count--; - update_finish(); - }); + for(const repository of registered_repositories()) { + promises.push(load_repository0(repository, false).then(() => callback_entry(repository)).catch(error => { + log.warn(LogCategory.I18N, "Failed to fetch repository %s. error: %o", repository.url, error); + })); } - - update_finish(); + await Promise.all(promises); } - export function select_translation(repository: TranslationRepository, entry: TranslationFile) { + export function select_translation(repository: TranslationRepository, entry: RepositoryTranslation) { const cfg = config.translation_config(); if(entry && repository) { - cfg.current_language = entry.info.name; + cfg.current_language = entry.name; cfg.current_repository_url = repository.url; - cfg.current_translation_url = entry.url; + cfg.current_translation_url = repository.url + entry.path; + cfg.current_translation_path = entry.path; } else { cfg.current_language = undefined; cfg.current_repository_url = undefined; cfg.current_translation_url = undefined; + cfg.current_translation_path = undefined; } config.save_translation_config(); @@ -318,7 +285,7 @@ namespace i18n { if(cfg.current_translation_url) { try { - await load_file(cfg.current_translation_url); + await load_file(cfg.current_translation_url, cfg.current_translation_path); } catch (error) { createErrorModal(tr("Translation System"), tr("Failed to load current selected translation file.") + "
File: " + cfg.current_translation_url + "
Error: " + error + "
" + tr("Using default fallback translations.")).open(); } diff --git a/shared/js/ui/modal/ModalConnect.ts b/shared/js/ui/modal/ModalConnect.ts index 95b8d513..13c5f68a 100644 --- a/shared/js/ui/modal/ModalConnect.ts +++ b/shared/js/ui/modal/ModalConnect.ts @@ -157,7 +157,7 @@ namespace Modals { let Regex = { //DOMAIN<:port> - DOMAIN: /^(localhost|((([a-zA-Z0-9_-]{0,63}\.){0,253})?[a-zA-Z0-9_-]{0,63}\.[a-zA-Z]{2,5}))(|:(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[0-5]?[0-9]{1,4}))$/, + DOMAIN: /^(localhost|((([a-zA-Z0-9_-]{0,63}\.){0,253})?[a-zA-Z0-9_-]{0,63}\.[a-zA-Z]{2,64}))(|:(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[0-5]?[0-9]{1,46}))$/, //IP<:port> IP_V4: /(^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(|:(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[0-5]?[0-9]{1,4}))$/, IP_V6: /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/, diff --git a/shared/js/ui/modal/ModalSettings.ts b/shared/js/ui/modal/ModalSettings.ts index 2e8e5675..c04d8179 100644 --- a/shared/js/ui/modal/ModalSettings.ts +++ b/shared/js/ui/modal/ModalSettings.ts @@ -611,7 +611,7 @@ namespace Modals { }; tag_loading.show(); - i18n.iterate_translations((repo, entry) => { + i18n.iterate_repositories(repo => { let repo_tag = tag_list.find("[repository=\"" + repo.unique_id + "\"]"); if (repo_tag.length == 0) { repo_tag = template.renderTag({ @@ -639,59 +639,63 @@ namespace Modals { tag_list.append(repo_tag); } - const tag = template.renderTag({ - type: "translation", - name: entry.info.name || entry.url, - id: repo.unique_id, - selected: i18n.config.translation_config().current_translation_url == entry.url - }); - tag.find(".button-info").on('click', e => { - e.preventDefault(); - - const info_modal = createModal({ - header: tr("Translation info"), - body: () => { - const tag = $("#settings-translations-list-entry-info").renderTag({ - type: "translation", - name: entry.info.name, - url: entry.url, - repository_name: repo.name, - contributors: entry.info.contributors || [] - }); - - tag.find(".button-info").on('click', () => display_repository_info(repo)); - - return tag; - }, - footer: () => { - let footer = $.spawn("div"); - footer.addClass("modal-button-group"); - footer.css("margin-top", "5px"); - footer.css("margin-bottom", "5px"); - footer.css("text-align", "right"); - - let buttonOk = $.spawn("button"); - buttonOk.text(tr("Close")); - buttonOk.click(() => info_modal.close()); - footer.append(buttonOk); - - return footer; - } + for(const translation of repo.translations) { + const tag = template.renderTag({ + type: "translation", + name: translation.name || translation.path, + id: repo.unique_id, + country_code: translation.country_code, + selected: i18n.config.translation_config().current_translation_path == translation.path }); - info_modal.open() - }); - tag.on('click', e => { - if (e.isDefaultPrevented()) return; - i18n.select_translation(repo, entry); - tag_list.find(".selected").removeClass("selected"); - tag.addClass("selected"); + tag.find(".button-info").on('click', e => { + e.preventDefault(); - restart_hint.show(); - }); - tag.insertAfter(repo_tag) - }, () => { - tag_loading.hide(); - }); + const info_modal = createModal({ + header: tr("Translation info"), + body: () => { + const tag = $("#settings-translations-list-entry-info").renderTag({ + type: "translation", + name: translation.name, + url: translation.path, + repository_name: repo.name, + contributors: translation.contributors || [] + }); + + tag.find(".button-info").on('click', () => display_repository_info(repo)); + + return tag; + }, + footer: () => { + let footer = $.spawn("div"); + footer.addClass("modal-button-group"); + footer.css("margin-top", "5px"); + footer.css("margin-bottom", "5px"); + footer.css("text-align", "right"); + + let buttonOk = $.spawn("button"); + buttonOk.text(tr("Close")); + buttonOk.click(() => info_modal.close()); + footer.append(buttonOk); + + return footer; + } + }); + info_modal.open() + }); + tag.on('click', e => { + if (e.isDefaultPrevented()) return; + i18n.select_translation(repo, translation); + tag_list.find(".selected").removeClass("selected"); + tag.addClass("selected"); + + restart_hint.show(); + }); + tag.insertAfter(repo_tag); + } + }).then(() => tag_loading.hide()).catch(error => { + console.error(error); + /* this should NEVER happen */ + }) } }; From 09d3b499770121dc0d3c037cb7d1f62cf52a8264 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Thu, 28 Mar 2019 17:30:00 +0100 Subject: [PATCH 08/13] Added API for file download --- shared/js/FileManager.ts | 147 +++++++++++++++++++++++++++++++++----- shared/js/crypto/src32.ts | 88 +++++++++++++++++++++++ shared/js/main.ts | 23 ++++++ 3 files changed, 242 insertions(+), 16 deletions(-) create mode 100644 shared/js/crypto/src32.ts diff --git a/shared/js/FileManager.ts b/shared/js/FileManager.ts index 62c51ec5..9cc40f2f 100644 --- a/shared/js/FileManager.ts +++ b/shared/js/FileManager.ts @@ -21,12 +21,11 @@ class FileListRequest { } namespace transfer { - export interface DownloadKey { + export interface TransferKey { client_transfer_id: number; server_transfer_id: number; key: string; - total_size: number; file_path: string; file_name: string; @@ -35,7 +34,23 @@ namespace transfer { hosts: string[], port: number; }; + + total_size: number; } + + export interface UploadOptions { + name: string; + path: string; + + channel?: ChannelEntry; + channel_password?: string; + + size: number; + overwrite: boolean; + } + + export type DownloadKey = TransferKey; + export type UploadKey = TransferKey; } class StreamedFileDownload { @@ -160,14 +175,58 @@ class RequestFileDownload { } } +class RequestFileUpload { + readonly transfer_key: transfer.UploadKey; + constructor(key: transfer.DownloadKey) { + this.transfer_key = key; + } + + async put_data(data: BufferSource | File) { + const form_data = new FormData(); + + if(data instanceof File) { + if(data.size != this.transfer_key.total_size) + throw "invalid size"; + + form_data.append("file", data); + } else { + const buffer = data; + if(buffer.byteLength != this.transfer_key.total_size) + throw "invalid size"; + + form_data.append("file", new Blob([buffer], { type: "application/octet-stream" })); + } + + await this.try_put(form_data, "https://" + this.transfer_key.peer.hosts[0] + ":" + this.transfer_key.peer.port); + } + + async try_put(data: FormData, url: string) : Promise { + const response = await fetch(url, { + method: 'POST', + cache: "no-cache", + mode: 'cors', + body: data, + headers: { + 'transfer-key': this.transfer_key.key, + 'Access-Control-Allow-Headers': '*', + 'Access-Control-Expose-Headers': '*' + } + }); + if(!response.ok) + throw (response.type == 'opaque' || response.type == 'opaqueredirect' ? "invalid cross origin flag! May target isn't a TeaSpeak server?" : response.statusText || "response is not ok"); + } +} + class FileManager extends connection.AbstractCommandHandler { handle: TSClient; icons: IconManager; avatars: AvatarManager; private listRequests: FileListRequest[] = []; - private pendingDownloadTransfers: transfer.DownloadKey[] = []; - private downloadCounter : number = 0; + private pending_download_requests: transfer.DownloadKey[] = []; + private pending_upload_requests: transfer.UploadKey[] = []; + + private transfer_counter : number = 0; constructor(client: TSClient) { super(client.serverConnection); @@ -190,6 +249,9 @@ class FileManager extends connection.AbstractCommandHandler { case "notifystartdownload": this.notifyStartDownload(command.arguments); return true; + case "notifystartupload": + this.notifyStartUpload(command.arguments); + return true; } return false; } @@ -260,27 +322,52 @@ class FileManager extends connection.AbstractCommandHandler { } - /******************************** File download ********************************/ + /******************************** File download/upload ********************************/ download_file(path: string, file: string, channel?: ChannelEntry, password?: string) : Promise { - const _this = this; - const transfer_data: transfer.DownloadKey = { file_name: file, - file_path: file, - client_transfer_id: this.downloadCounter++ + file_path: path, + client_transfer_id: this.transfer_counter++ } as any; - this.pendingDownloadTransfers.push(transfer_data); + this.pending_download_requests.push(transfer_data); return new Promise((resolve, reject) => { - transfer_data["_promiseCallback"] = resolve; - _this.handle.serverConnection.send_command("ftinitdownload", { + transfer_data["_callback"] = resolve; + this.handle.serverConnection.send_command("ftinitdownload", { "path": path, "name": file, "cid": (channel ? channel.channelId : "0"), "cpw": (password ? password : ""), "clientftfid": transfer_data.client_transfer_id }).catch(reason => { - _this.pendingDownloadTransfers.remove(transfer_data); + this.pending_download_requests.remove(transfer_data); + reject(reason); + }) + }); + } + + upload_file(options: transfer.UploadOptions) : Promise { + const transfer_data: transfer.UploadKey = { + file_path: options.path, + file_name: options.name, + client_transfer_id: this.transfer_counter++, + total_size: options.size + } as any; + + this.pending_upload_requests.push(transfer_data); + return new Promise((resolve, reject) => { + transfer_data["_callback"] = resolve; + this.handle.serverConnection.send_command("ftinitupload", { + "path": options.path, + "name": options.name, + "cid": (options.channel ? options.channel.channelId : "0"), + "cpw": options.channel_password || "", + "clientftfid": transfer_data.client_transfer_id, + "size": options.size, + "overwrite": options.overwrite, + "resume": false + }).catch(reason => { + this.pending_upload_requests.remove(transfer_data); reject(reason); }) }); @@ -290,7 +377,7 @@ class FileManager extends connection.AbstractCommandHandler { json = json[0]; let transfer: transfer.DownloadKey; - for(let e of this.pendingDownloadTransfers) + for(let e of this.pending_download_requests) if(e.client_transfer_id == json["clientftfid"]) { transfer = e; break; @@ -311,8 +398,36 @@ class FileManager extends connection.AbstractCommandHandler { if(transfer.peer.hosts[0].length == 0 || transfer.peer.hosts[0] == '0.0.0.0') transfer.peer.hosts[0] = this.handle.serverConnection._remote_address.host; - (transfer["_promiseCallback"] as (val: transfer.DownloadKey) => void)(transfer); - this.pendingDownloadTransfers.remove(transfer); + (transfer["_callback"] as (val: transfer.DownloadKey) => void)(transfer); + this.pending_download_requests.remove(transfer); + } + + private notifyStartUpload(json) { + json = json[0]; + + let transfer: transfer.UploadKey; + for(let e of this.pending_upload_requests) + if(e.client_transfer_id == json["clientftfid"]) { + transfer = e; + break; + } + + transfer.server_transfer_id = json["serverftfid"]; + transfer.key = json["ftkey"]; + + transfer.peer = { + hosts: (json["ip"] || "").split(","), + port: json["port"] + }; + + if(transfer.peer.hosts.length == 0) + transfer.peer.hosts.push("0.0.0.0"); + + if(transfer.peer.hosts[0].length == 0 || transfer.peer.hosts[0] == '0.0.0.0') + transfer.peer.hosts[0] = this.handle.serverConnection._remote_address.host; + + (transfer["_callback"] as (val: transfer.UploadKey) => void)(transfer); + this.pending_upload_requests.remove(transfer); } } diff --git a/shared/js/crypto/src32.ts b/shared/js/crypto/src32.ts new file mode 100644 index 00000000..8122a8a2 --- /dev/null +++ b/shared/js/crypto/src32.ts @@ -0,0 +1,88 @@ +class Crc32 { + private static readonly lookup = [ + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, + 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, + 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, + 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, + 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, + 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, + 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, + 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, + 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, + 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, + 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, + 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, + 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, + 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, + 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, + 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, + 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, + 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, + 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + ]; + + private crc: number; + constructor() { + this.crc = -1 >>> 0; + } + + update(data: ArrayBufferLike) { + const dataView = new Uint8Array(data, 0); + const len = dataView.length; + for (let i = 0; i < len; i++) { + this.crc = (this.crc >>> 8) ^ Crc32.lookup[(this.crc ^ dataView[i]) & 0xFF]; + } + }; + + digest(radix: number) { + const buffer = new ArrayBuffer(4); + const dv = new DataView(buffer); + dv.setUint32(0, ~this.crc >>> 0, false); + return dv.getUint32(0).toString(radix || 16); + }; +} \ No newline at end of file diff --git a/shared/js/main.ts b/shared/js/main.ts index ce1d90ac..998cebd8 100644 --- a/shared/js/main.ts +++ b/shared/js/main.ts @@ -288,6 +288,29 @@ function main() { Modals.spawnAvatarList(globalClient); }, 1000); */ + window.test_upload = () => { + const data = "Hello World"; + globalClient.fileManager.upload_file({ + size: data.length, + overwrite: true, + channel: globalClient.getClient().currentChannel(), + name: '/HelloWorld.txt', + path: '' + }).then(key => { + console.log("Got key: %o", key); + const upload = new RequestFileUpload(key); + + const buffer = new Uint8Array(data.length); + { + for(let index = 0; index < data.length; index++) + buffer[index] = data.charCodeAt(index); + } + + upload.put_data(buffer).catch(error => { + console.error(error); + }); + }) + }; } loader.register_task(loader.Stage.LOADED, { From 077f5a66ae8d4bde1c13c14f0a013df8d346d04e Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Thu, 28 Mar 2019 17:30:15 +0100 Subject: [PATCH 09/13] Updated changelog --- ChangeLog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index ee0a5559..c107e807 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -7,6 +7,8 @@ - Fixed context menu overflowing and going out of the side - Improved host banner url revoke (only revoke after a new one has been generated) - Added some fancy console messages + - Added country icons to the translation tab + - Decreased required bandwidth on translation loading * **17.03.19** - Using VAD by default instead of PPT From 2dbbd32d9f3eac3dff57717e4a61bd9b9b955e54 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Thu, 28 Mar 2019 17:38:41 +0100 Subject: [PATCH 10/13] Fixed rename errors --- shared/js/main.ts | 2 +- shared/js/permission/PermissionManager.ts | 6 +++--- shared/js/ui/htmltags.ts | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/shared/js/main.ts b/shared/js/main.ts index 998cebd8..82d93ae0 100644 --- a/shared/js/main.ts +++ b/shared/js/main.ts @@ -288,7 +288,7 @@ function main() { Modals.spawnAvatarList(globalClient); }, 1000); */ - window.test_upload = () => { + (window).test_upload = () => { const data = "Hello World"; globalClient.fileManager.upload_file({ size: data.length, diff --git a/shared/js/permission/PermissionManager.ts b/shared/js/permission/PermissionManager.ts index 5f345b80..4a6da2fc 100644 --- a/shared/js/permission/PermissionManager.ts +++ b/shared/js/permission/PermissionManager.ts @@ -696,7 +696,7 @@ class PermissionManager extends connection.AbstractCommandHandler { let client = parseInt(json[0]["cldbid"]); let permissions = PermissionManager.parse_permission_bulk(json, this); for(let req of this.requests_client_permissions.slice(0)) { - if(req.client_avatar_id == client) { + if(req.client_id == client) { this.requests_client_permissions.remove(req); req.promise.resolved(permissions); } @@ -705,7 +705,7 @@ class PermissionManager extends connection.AbstractCommandHandler { requestClientPermissions(client_id: number) : Promise { for(let request of this.requests_client_permissions) - if(request.client_avatar_id == client_id && request.promise.time() + 1000 > Date.now()) + if(request.client_id == client_id && request.promise.time() + 1000 > Date.now()) return request.promise; let request: TeaPermissionRequest = {} as any; @@ -725,7 +725,7 @@ class PermissionManager extends connection.AbstractCommandHandler { requestClientChannelPermissions(client_id: number, channel_id: number) : Promise { for(let request of this.requests_client_channel_permissions) - if(request.client_avatar_id == client_id && request.channel_id == channel_id && request.promise.time() + 1000 > Date.now()) + if(request.client_id == client_id && request.channel_id == channel_id && request.promise.time() + 1000 > Date.now()) return request.promise; let request: TeaPermissionRequest = {} as any; diff --git a/shared/js/ui/htmltags.ts b/shared/js/ui/htmltags.ts index 4cd11d5e..fa65546b 100644 --- a/shared/js/ui/htmltags.ts +++ b/shared/js/ui/htmltags.ts @@ -30,8 +30,8 @@ namespace htmltags { /* build the opening tag:
*/ result = result + "
Date: Thu, 28 Mar 2019 17:46:20 +0100 Subject: [PATCH 11/13] Fixed serveredit --- shared/js/ui/modal/ModalServerEdit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/js/ui/modal/ModalServerEdit.ts b/shared/js/ui/modal/ModalServerEdit.ts index 9126bd05..18bb0e89 100644 --- a/shared/js/ui/modal/ModalServerEdit.ts +++ b/shared/js/ui/modal/ModalServerEdit.ts @@ -5,7 +5,7 @@ namespace Modals { let properties: ServerProperties = {} as ServerProperties; //The changes properties const render_properties = {}; - Object.assign(render_properties, properties); + Object.assign(render_properties, server.properties); render_properties["virtualserver_icon"] = server.channelTree.client.fileManager.icons.generateTag(server.properties.virtualserver_icon_id); const modal_template = $("#tmpl_server_edit").renderTag(render_properties); From 3ec79596209f84915adbcd39ba99076a2e63fb56 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Thu, 28 Mar 2019 17:54:46 +0100 Subject: [PATCH 12/13] Fixed changelog date --- ChangeLog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index c107e807..097edfb1 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,5 @@ # Changelog: -* **XX.XX.XX** +* **28.03.19** - Improved icon and avatar cache handling - Added an icon manager - Fixed bookmark create modal style From ff47cfe621f080ff0c41df4137e6217bec85b1f0 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Thu, 28 Mar 2019 22:20:29 +0100 Subject: [PATCH 13/13] Fixing style issues related to firefox --- shared/css/static/modal-permissions.scss | 4 ++++ shared/css/static/modal-playlist.scss | 4 +++- shared/css/static/modals.scss | 4 ++++ shared/css/static/ts/tab.scss | 10 ++++++++++ shared/html/templates.html | 2 +- vendor/bbcode | 2 +- 6 files changed, 23 insertions(+), 3 deletions(-) diff --git a/shared/css/static/modal-permissions.scss b/shared/css/static/modal-permissions.scss index 7a2ff463..6eb5ebac 100644 --- a/shared/css/static/modal-permissions.scss +++ b/shared/css/static/modal-permissions.scss @@ -1,8 +1,11 @@ permission-editor { display: flex; flex-direction: column; + flex-grow: 1; flex-shrink: 1; + + min-height: 0; } @@ -260,6 +263,7 @@ permission-editor { flex-grow: 1; flex-shrink: 1; + min-height: 0; &.container-mode-unset { background-color: lightgray; diff --git a/shared/css/static/modal-playlist.scss b/shared/css/static/modal-playlist.scss index a05ce0b3..7ee0cd1c 100644 --- a/shared/css/static/modal-playlist.scss +++ b/shared/css/static/modal-playlist.scss @@ -155,6 +155,7 @@ display: flex; flex-direction: column; justify-content: stretch; + min-height: 0; .tab-content { padding: 0; /* override tab-content setting */ @@ -228,13 +229,14 @@ text-align: center; } - .tab-content, x-content { + x-content { overflow-y: hidden; display: flex; flex-direction: column; } .container-songs { + width: 100%; display: flex; flex-direction: column; padding: 5px; diff --git a/shared/css/static/modals.scss b/shared/css/static/modals.scss index 9e1e7087..e6195128 100644 --- a/shared/css/static/modals.scss +++ b/shared/css/static/modals.scss @@ -14,6 +14,8 @@ body { modal-body { display: flex; flex-direction: column; + + min-height: 10px; } .modal { @@ -64,6 +66,7 @@ modal-body { .modal-content { /* max-height: 500px; */ + min-height: 0; /* required for moz */ flex-direction: column; justify-content: stretch; @@ -90,6 +93,7 @@ modal-body { flex-shrink: 1; display: flex; flex-direction: column; + min-height: 0; input.is-invalid { background-image: linear-gradient(0deg, #d50000 2px, rgba(213, 0, 0, 0) 0), linear-gradient(0deg, rgba(241, 1, 1, 0.61) 1px, transparent 0); diff --git a/shared/css/static/ts/tab.scss b/shared/css/static/ts/tab.scss index a312ae96..ed9616e2 100644 --- a/shared/css/static/ts/tab.scss +++ b/shared/css/static/ts/tab.scss @@ -6,6 +6,8 @@ x-tab { display:none } flex-direction: column; display: flex; flex-grow: 1; + + min-height: 220px; /* min the header */ } .tab div * { @@ -25,9 +27,17 @@ x-tab { display:none } flex-grow: 1; x-content { + min-height: 0; overflow-y: auto; + width: 100%; } + + @-moz-document url-prefix() { + x-content { + height: 100%; + } + } } /* diff --git a/shared/html/templates.html b/shared/html/templates.html index ddc89598..e0b1799c 100644 --- a/shared/html/templates.html +++ b/shared/html/templates.html @@ -1779,7 +1779,7 @@
-
+