diff --git a/addons/__init__.py b/addons/__init__.py deleted file mode 100644 index 9bcfb4e..0000000 --- a/addons/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .lyrics import LYRICS_PLATFORMS -from .placeholders import Placeholders -from .settings import Settings \ No newline at end of file diff --git a/addons/placeholders.py b/addons/placeholders.py deleted file mode 100644 index a3a2a64..0000000 --- a/addons/placeholders.py +++ /dev/null @@ -1,63 +0,0 @@ -"""MIT License - -Copyright (c) 2023 - present Vocard Development - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -from discord.ext import commands -from re import findall -from importlib import import_module - -class Placeholders: - def __init__(self, bot: commands.Bot) -> None: - self.bot = bot - self.voicelink = import_module("voicelink") - self.variables = { - "guilds": self.guilds_count, - "users": self.users_count, - "players": self.players_count, - "nodes": self.nodes_count - } - - def guilds_count(self) -> int: - return len(self.bot.guilds) - - def users_count(self) -> int: - return len(self.bot.users) - - def players_count(self) -> int: - count = 0 - for node in self.voicelink.NodePool._nodes.values(): - count += len(node._players) - - return count - - def nodes_count(self): - return len(self.voicelink.NodePool._nodes) - - def replace(self, msg: str) -> str: - keys = findall(r'@@(.*?)@@', msg) - - for key in keys: - value = self.variables.get(key.lower(), None) - if value: - msg = msg.replace(f"@@{key}@@", str(value())) - - return msg \ No newline at end of file diff --git a/addons/settings.py b/addons/settings.py deleted file mode 100644 index ff79807..0000000 --- a/addons/settings.py +++ /dev/null @@ -1,59 +0,0 @@ -"""MIT License - -Copyright (c) 2023 - present Vocard Development - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -import os - -from dotenv import load_dotenv -from typing import ( - Dict, - List, - Any, - Union -) - -load_dotenv() - -class Settings: - def __init__(self, settings: Dict) -> None: - self.token: str = settings.get("token") or os.getenv("TOKEN") - self.client_id: int = int(settings.get("client_id", 0)) or int(os.getenv("CLIENT_ID")) - self.genius_token: str = settings.get("genius_token") or os.getenv("GENIUS_TOKEN") - self.mongodb_url: str = settings.get("mongodb_url") or os.getenv("MONGODB_URL") - self.mongodb_name: str = settings.get("mongodb_name") or os.getenv("MONGODB_NAME") - - self.invite_link: str = "https://discord.gg/wRCgB7vBQv" - self.nodes: Dict[str, Dict[str, Union[str, int, bool]]] = settings.get("nodes", {}) - self.max_queue: int = settings.get("default_max_queue", 1000) - self.bot_prefix: str = settings.get("prefix", "") - self.activity: List[Dict[str, str]] = settings.get("activity", [{"listen": "/help"}]) - self.logging: Dict[Union[str, Dict[str, Union[str, bool]]]] = settings.get("logging", {}) - self.embed_color: str = int(settings.get("embed_color", "0xb3b3b3"), 16) - self.bot_access_user: List[int] = settings.get("bot_access_user", []) - self.sources_settings: Dict[Dict[str, str]] = settings.get("sources_settings", {}) - self.cooldowns_settings: Dict[str, List[int]] = settings.get("cooldowns", {}) - self.aliases_settings: Dict[str, List[str]] = settings.get("aliases", {}) - self.controller: Dict[str, Dict[str, Any]] = settings.get("default_controller", {}) - self.voice_status_template: str = settings.get("default_voice_status_template", "") - self.lyrics_platform: str = settings.get("lyrics_platform", "A_ZLyrics").lower() - self.ipc_client: Dict[str, Union[str, bool, int]] = settings.get("ipc_client", {}) - self.version: str = settings.get("version", "") \ No newline at end of file diff --git a/cogs/basic.py b/cogs/basic.py index af1fea9..a66f70c 100644 --- a/cogs/basic.py +++ b/cogs/basic.py @@ -21,39 +21,33 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import discord, voicelink, re +import re +import discord +import voicelink from io import StringIO +from validators import url from discord import app_commands from discord.ext import commands from function import ( - settings, - send, - time as ctime, - format_time, - get_source, - get_user, - get_lang, - truncate_string, cooldown_check, get_aliases, logger ) -from voicelink import SearchType, LoopType -from addons import LYRICS_PLATFORMS -from views import SearchView, ListView, LinkView, LyricsView, HelpView -from validators import url +from voicelink import MongoDBHandler, LangHandler, Config +from voicelink.views import SearchView, QueueView, LinkView, LyricsView, HelpView +from voicelink.utils import format_ms, format_to_ms, truncate_string, dispatch_message, send_localized_message async def nowplay(ctx: commands.Context, player: voicelink.Player): track = player.current if not track: - return await send(ctx, 'noTrackPlaying', ephemeral=True) + return await send_localized_message(ctx, 'player.errors.noTrackPlaying', ephemeral=True) - texts = await get_lang(ctx.guild.id, "nowplayingDesc", "nowplayingField", "nowplayingLink") + texts = await LangHandler.get_lang(ctx.guild.id, "player.playback.nowplayingDesc", "player.playback.nowplayingField", "player.playback.nowplayingLink") upnext = "\n".join(f"`{index}.` `[{track.formatted_length}]` [{truncate_string(track.title)}]({track.uri})" for index, track in enumerate(player.queue.tracks()[:2], start=2)) - embed = discord.Embed(description=texts[0].format(track.title), color=settings.embed_color) + embed = discord.Embed(description=texts[0].format(track.title), color=Config().embed_color) embed.set_author( name=track.requester.display_name, icon_url=track.requester.display_avatar.url @@ -65,9 +59,9 @@ async def nowplay(ctx: commands.Context, player: voicelink.Player): pbar = "".join(":radio_button:" if i == round(player.position // round(track.length // 15)) else "โ–ฌ" for i in range(15)) icon = ":red_circle:" if track.is_stream else (":pause_button:" if player.is_paused else ":arrow_forward:") - embed.add_field(name="\u2800", value=f"{icon} {pbar} **[{ctime(player.position)}/{track.formatted_length}]**", inline=False) + embed.add_field(name="\u2800", value=f"{icon} {pbar} **[{format_ms(player.position)}/{track.formatted_length}]**", inline=False) - return await send(ctx, embed, view=LinkView(texts[2].format(track.source.title()), track.emoji, track.uri)) + return await dispatch_message(ctx, embed, view=LinkView(texts[2].format(track.source.title()), track.emoji, track.uri)) class Basic(commands.Cog): def __init__(self, bot: commands.Bot) -> None: @@ -103,7 +97,7 @@ class Basic(commands.Cog): return [app_commands.Choice(name=truncate_string(f"๐ŸŽต {track.author} - {track.title}", 100), value=truncate_string(f"{track.author} - {track.title}", 100)) for track in tracks] - history = {track["identifier"]: track for track_id in reversed(await get_user(interaction.user.id, "history")) if (track := voicelink.decode(track_id))["uri"]} + history = {track["identifier"]: track for track_id in reversed(await MongoDBHandler.get_user(interaction.user.id, d_type="history")) if (track := voicelink.Track.decode(track_id))["uri"]} return [app_commands.Choice(name=truncate_string(f"๐Ÿ•’ {track['author']} - {track['title']}", 100), value=track['uri']) for track in history.values() if len(track['uri']) <= 100][:25] @commands.hybrid_command(name="connect", aliases=get_aliases("connect")) @@ -114,9 +108,9 @@ class Basic(commands.Cog): try: player = await voicelink.connect_channel(ctx, channel) except discord.errors.ClientException: - return await send(ctx, "alreadyConnected") + return await send_localized_message(ctx, "voice.connection.alreadyConnected") - await send(ctx, 'connect', player.channel) + await send_localized_message(ctx, "voice.connection.connect", player.channel) @commands.hybrid_command(name="play", aliases=get_aliases("play")) @app_commands.describe( @@ -133,27 +127,26 @@ class Basic(commands.Cog): player = await voicelink.connect_channel(ctx) if not player.is_user_join(ctx.author): - return await send(ctx, "notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) + return await send_localized_message(ctx, "voice.connection.notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) if ctx.interaction: await ctx.interaction.response.defer() tracks = await player.get_tracks(query, requester=ctx.author) if not tracks: - return await send(ctx, "noTrackFound") + return await send_localized_message(ctx, "player.errors.noTrackFound") try: if isinstance(tracks, voicelink.Playlist): - index = await player.add_track(tracks.tracks, start_time=format_time(start), end_time=format_time(end)) - await send(ctx, "playlistLoad", tracks.name, index) + index = await player.add_track(tracks.tracks, start_time=format_to_ms(start), end_time=format_to_ms(end)) + await send_localized_message(ctx, "player.playback.playlistLoad", tracks.name, index) else: - position = await player.add_track(tracks[0], start_time=format_time(start), end_time=format_time(end)) - texts = await get_lang(ctx.guild.id, "live", "trackLoad_pos", "trackLoad") - + position = await player.add_track(tracks[0], start_time=format_to_ms(start), end_time=format_to_ms(end)) + texts = await LangHandler.get_lang(ctx.guild.id, "common.status.live", "player.playback.trackLoadPos", "player.playback.trackLoad") stream_content = f"`{texts[0]}`" if tracks[0].is_stream else "" additional_content = texts[1] if position >= 1 and player.is_playing else texts[2] - await send( + await dispatch_message( ctx, stream_content + additional_content, tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatted_length, @@ -176,32 +169,32 @@ class Basic(commands.Cog): query = message.attachments[0].url if not query: - return await send(interaction, "noPlaySource", ephemeral=True) + return await send_localized_message(interaction, "player.errors.noPlaySource", ephemeral=True) player: voicelink.Player = interaction.guild.voice_client if not player: player = await voicelink.connect_channel(interaction) if not player.is_user_join(interaction.user): - return await send(interaction, "notInChannel", interaction.user.mention, player.channel.mention, ephemeral=True) + return await send_localized_message(interaction, "voice.connection.notInChannel", interaction.user.mention, player.channel.mention, ephemeral=True) await interaction.response.defer() tracks = await player.get_tracks(query, requester=interaction.user) if not tracks: - return await send(interaction, "noTrackFound") + return await send_localized_message(interaction, "player.errors.noTrackFound") try: if isinstance(tracks, voicelink.Playlist): index = await player.add_track(tracks.tracks) - await send(interaction, "playlistLoad", tracks.name, index) + await send_localized_message(interaction, "player.playback.playlistLoad", tracks.name, index) else: position = await player.add_track(tracks[0]) - texts = await get_lang(interaction.guild.id, "live", "trackLoad_pos", "trackLoad") + texts = await LangHandler.get_lang(interaction.guild.id, "common.status.live", "player.playback.trackLoadPos", "player.playback.trackLoad") stream_content = f"`{texts[0]}`" if tracks[0].is_stream else "" additional_content = texts[1] if position >= 1 and player.is_playing else texts[2] - await send( + await dispatch_message( interaction, stream_content + additional_content, tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatted_length, @@ -218,31 +211,31 @@ class Basic(commands.Cog): ) @app_commands.choices(platform=[ app_commands.Choice(name=search_type.display_name, value=search_type.name) - for search_type in SearchType + for search_type in voicelink.SearchType ]) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) - async def search(self, ctx: commands.Context, *, query: str, platform: str = SearchType.YOUTUBE.name): + async def search(self, ctx: commands.Context, *, query: str, platform: str = Config().search_platform.name): "Searches your query and displays the results." player: voicelink.Player = ctx.guild.voice_client if not player: player = await voicelink.connect_channel(ctx) if not player.is_user_join(ctx.author): - return await send(ctx, "notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) + return await send_localized_message(ctx, "voice.connection.notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) if url(query): - return await send(ctx, "noLinkSupport", ephemeral=True) + return await send_localized_message(ctx, "search.noLinkSupport", ephemeral=True) - search_type: SearchType = SearchType.match(platform) or SearchType.YOUTUBE + search_type: voicelink.SearchType = voicelink.SearchType.match(platform) or Config().search_platform tracks = await player.get_tracks(query=query, requester=ctx.author, search_type=search_type) if not tracks: - return await send(ctx, "noTrackFound") + return await send_localized_message(ctx, "player.errors.noTrackFound") - texts = await get_lang(ctx.guild.id, "searchTitle", "searchDesc", "live", "trackLoad_pos", "trackLoad", "searchWait", "searchSuccess") + texts = await LangHandler.get_lang(ctx.guild.id, "search.title", "search.desc", "common.status.live", "player.playback.trackLoadPos", "player.playback.trackLoad", "search.wait", "search.success") query_track = "\n".join(f"`{index}.` `[{track.formatted_length}]` **{track.title[:35]}**" for index, track in enumerate(tracks[0:10], start=1)) - embed = discord.Embed(title=texts[0].format(query), description=texts[1].format(get_source(search_type.display_name, "emoji"), search_type.display_name, len(tracks[0:10]), query_track), color=settings.embed_color) + embed = discord.Embed(title=texts[0].format(query), description=texts[1].format(Config().get_source_config(search_type.display_name, "emoji"), search_type.display_name, len(tracks[0:10]), query_track), color=Config().embed_color) view = SearchView(tracks=tracks[0:10], texts=[texts[5], texts[6]]) - view.response = await send(ctx, embed, view=view, ephemeral=True) + view.response = await dispatch_message(ctx, embed, view=view, ephemeral=True) await view.wait() if view.values is not None: @@ -251,7 +244,7 @@ class Basic(commands.Cog): track = tracks[int(value.split(". ")[0]) - 1] position = await player.add_track(track) msg += (f"`{texts[2]}`" if track.is_stream else "") + (texts[3].format(track.title, track.uri, track.author, track.formatted_length, position) if position >= 1 else texts[4].format(track.title, track.uri, track.author, track.formatted_length)) - await send(ctx, msg) + await dispatch_message(ctx, msg) if not player.is_playing: await player.do_next() @@ -271,27 +264,27 @@ class Basic(commands.Cog): player = await voicelink.connect_channel(ctx) if not player.is_user_join(ctx.author): - return await send(ctx, "notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) + return await send_localized_message(ctx, "voice.connection.notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) if ctx.interaction: await ctx.interaction.response.defer() tracks = await player.get_tracks(query, requester=ctx.author) if not tracks: - return await send(ctx, "noTrackFound") + return await send_localized_message(ctx, "player.errors.noTrackFound") try: if isinstance(tracks, voicelink.Playlist): - index = await player.add_track(tracks.tracks, start_time=format_time(start), end_time=format_time(end), at_front=True) - await send(ctx, "playlistLoad", tracks.name, index) + index = await player.add_track(tracks.tracks, start_time=format_to_ms(start), end_time=format_to_ms(end), at_front=True) + await send_localized_message(ctx, "player.playback.playlistLoad", tracks.name, index) else: - position = await player.add_track(tracks[0], start_time=format_time(start), end_time=format_time(end), at_front=True) - texts = await get_lang(ctx.guild.id, "live", "trackLoad_pos", "trackLoad") + position = await player.add_track(tracks[0], start_time=format_to_ms(start), end_time=format_to_ms(end), at_front=True) + texts = await LangHandler.get_lang(ctx.guild.id, "common.status.live", "player.playback.trackLoadPos", "player.playback.trackLoad") stream_content = f"`{texts[0]}`" if tracks[0].is_stream else "" additional_content = texts[1] if position >= 1 and player.is_playing else texts[2] - await send( + await dispatch_message( ctx, stream_content + additional_content, tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatted_length, @@ -315,26 +308,26 @@ class Basic(commands.Cog): player = await voicelink.connect_channel(ctx) if not player.is_privileged(ctx.author): - return await send(ctx, "missingFunctionPerm", ephemeral=True) + return await send_localized_message(ctx, "permissions.missingFunction", ephemeral=True) if ctx.interaction: await ctx.interaction.response.defer() tracks = await player.get_tracks(query, requester=ctx.author) if not tracks: - return await send(ctx, "noTrackFound") + return await send_localized_message(ctx, "player.errors.noTrackFound") try: if isinstance(tracks, voicelink.Playlist): - index = await player.add_track(tracks.tracks, start_time=format_time(start), end_time=format_time(end), at_front=True) - await send(ctx, "playlistLoad", tracks.name, index) + index = await player.add_track(tracks.tracks, start_time=format_to_ms(start), end_time=format_to_ms(end), at_front=True) + await send_localized_message(ctx, "player.playback.playlistLoad", tracks.name, index) else: - texts = await get_lang(ctx.guild.id, "live", "trackLoad") - await player.add_track(tracks[0], start_time=format_time(start), end_time=format_time(end), at_front=True) + texts = await LangHandler.get_lang(ctx.guild.id, "common.status.live", "player.playback.trackLoad") + await player.add_track(tracks[0], start_time=format_to_ms(start), end_time=format_to_ms(end), at_front=True) stream_content = f"`{texts[0]}`" if tracks[0].is_stream else "" - await send( + await dispatch_message( ctx, stream_content + texts[1], tracks[0].title, tracks[0].uri, tracks[0].author, tracks[0].formatted_length, @@ -351,21 +344,21 @@ class Basic(commands.Cog): "Pause the music." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if player.is_paused: - return await send(ctx, "pauseError", ephemeral=True) + return await send_localized_message(ctx, "player.controls.pause.error", ephemeral=True) if not player.is_privileged(ctx.author): if ctx.author in player.pause_votes: - return await send(ctx, "voted", ephemeral=True) + return await send_localized_message(ctx, "voting.voted", ephemeral=True) player.pause_votes.add(ctx.author) if len(player.pause_votes) < (required := player.required()): - return await send(ctx, "pauseVote", ctx.author, len(player.pause_votes), required) + return await send_localized_message(ctx, "player.controls.pause.vote", ctx.author, len(player.pause_votes), required) await player.set_pause(True, ctx.author) - await send(ctx, "paused", ctx.author) + await send_localized_message(ctx, player.controls.pause.success, ctx.author) @commands.hybrid_command(name="resume", aliases=get_aliases("resume")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) @@ -373,21 +366,21 @@ class Basic(commands.Cog): "Resume the music." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_paused: - return await send(ctx, "resumeError") + return await send_localized_message(ctx, "player.controls.resume.error") if not player.is_privileged(ctx.author): if ctx.author in player.resume_votes: - return await send(ctx, "voted", ephemeral=True) + return await send_localized_message(ctx, "voting.voted", ephemeral=True) player.resume_votes.add(ctx.author) if len(player.resume_votes) < (required := player.required()): - return await send(ctx, "resumeVote", ctx.author, len(player.resume_votes), required) + return await send_localized_message(ctx, "player.controls.resume.vote", ctx.author, len(player.resume_votes), required) await player.set_pause(False, ctx.author) - await send(ctx, "resumed", ctx.author) + await send_localized_message(ctx, "player.controls.resume.success", ctx.author) @commands.hybrid_command(name="skip", aliases=get_aliases("skip")) @app_commands.describe(index="Enter a index that you want to skip to.") @@ -396,28 +389,28 @@ class Basic(commands.Cog): "Skips to the next song or skips to the specified song." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.node._available: - return await send(ctx, "nodeReconnect") + return await send_localized_message(ctx, "player.errors.nodeReconnect") if not player.is_playing: - return await send(ctx, "skipError", ephemeral=True) + return await send_localized_message(ctx, "player.controls.skip.error", ephemeral=True) if not player.is_privileged(ctx.author): if ctx.author == player.current.requester: pass elif ctx.author in player.skip_votes: - return await send(ctx, "voted", ephemeral=True) + return await send_localized_message(ctx, "voting.voted", ephemeral=True) else: player.skip_votes.add(ctx.author) if len(player.skip_votes) < (required := player.required()): - return await send(ctx, "skipVote", ctx.author, len(player.skip_votes), required) + return await send_localized_message(ctx, "player.controls.skip.vote", ctx.author, len(player.skip_votes), required) if index: player.queue.skipto(index) - await send(ctx, "skipped", ctx.author) + await send_localized_message(ctx, "player.controls.skip.success", ctx.author) if player.queue._repeat.mode == voicelink.LoopType.TRACK: await player.set_repeat(voicelink.LoopType.OFF) @@ -430,18 +423,18 @@ class Basic(commands.Cog): "Skips back to the previous song or skips to the specified previous song." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.node._available: - return await send(ctx, "nodeReconnectode") + return await send_localized_message(ctx, "player.errors.nodeReconnectode") if not player.is_privileged(ctx.author): if ctx.author in player.previous_votes: - return await send(ctx, "voted", ephemeral=True) + return await send_localized_message(ctx, "voting.voted", ephemeral=True) player.previous_votes.add(ctx.author) if len(player.previous_votes) < (required := player.required()): - return await send(ctx, "backVote", ctx.author, len(player.previous_votes), required) + return await send_localized_message(ctx, "player.controls.back.vote", ctx.author, len(player.previous_votes), required) if not player.is_playing: player.queue.backto(index) @@ -450,7 +443,7 @@ class Basic(commands.Cog): player.queue.backto(index + 1) await player.stop() - await send(ctx, "backed", ctx.author) + await send_localized_message(ctx, "player.controls.back.success", ctx.author) if player.queue._repeat.mode == voicelink.LoopType.TRACK: await player.set_repeat(voicelink.LoopType.OFF) @@ -461,19 +454,19 @@ class Basic(commands.Cog): "Change the player position." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPosPerm", ephemeral=True) + return await send_localized_message(ctx, "permissions.missingPosition", ephemeral=True) if not player.current or player.position == 0: - return await send(ctx, "noTrackPlaying", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noTrackPlaying", ephemeral=True) - if not (num := format_time(position)): - return await send(ctx, "timeFormatError", ephemeral=True) + if not (num := format_to_ms(position)): + return await send_localized_message(ctx, "time.formatError", ephemeral=True) await player.seek(num, ctx.author) - await send(ctx, "seek", position) + await send_localized_message(ctx, "player.controls.seek", position) @commands.hybrid_group( name="queue", @@ -486,15 +479,15 @@ class Basic(commands.Cog): "Display the players queue songs in your queue." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_user_join(ctx.author): - return await send(ctx, "notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) + return await send_localized_message(ctx, "voice.connection.notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) if player.queue.is_empty: return await nowplay(ctx, player) - view = ListView(player=player, author=ctx.author) - view.response = await send(ctx, await view.build_embed(), view=view) + view = QueueView(player=player, author=ctx.author) + view.response = await dispatch_message(ctx, await view.build_embed(), view=view) @queue.command(name="export", aliases=get_aliases("export")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) @@ -502,13 +495,13 @@ class Basic(commands.Cog): "Exports the entire queue to a text file" player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_user_join(ctx.author): - return await send(ctx, "notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) + return await send_localized_message(ctx, "voice.connection.notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) if player.queue.is_empty and not player.current: - return await send(ctx, "noTrackPlaying", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noTrackPlaying", ephemeral=True) await ctx.defer() @@ -518,7 +511,7 @@ class Basic(commands.Cog): total_length = 0 for index, track in enumerate(tracks, start=1): - temp += f"{index}. {track.title} [{ctime(track.length)}]\n" + temp += f"{index}. {track.title} [{format_ms(track.length)}]\n" raw += track.track_id if index != len(tracks): raw += "," @@ -527,7 +520,7 @@ class Basic(commands.Cog): temp = "!Remember do not change this file!\n------------->Info<-------------\nGuild: {} ({})\nRequester: {} ({})\nTracks: {} - {}\n------------>Tracks<------------\n".format( ctx.guild.name, ctx.guild.id, ctx.author.display_name, ctx.author.id, - len(tracks), ctime(total_length) + len(tracks), format_ms(total_length) ) + temp temp += raw @@ -542,19 +535,19 @@ class Basic(commands.Cog): player = await voicelink.connect_channel(ctx) if not player.is_user_join(ctx.author): - return await send(ctx, "notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) + return await send_localized_message(ctx, "voice.connection.notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) try: bytes = await attachment.read() track_ids = bytes.split(b"\n")[-1] track_ids = track_ids.decode().split(",") - tracks = [voicelink.Track(track_id=track_id, info=voicelink.decode(track_id), requester=ctx.author) for track_id in track_ids] + tracks = [voicelink.Track(track_id=track_id, info=voicelink.Track.decode(track_id), requester=ctx.author) for track_id in track_ids] if not tracks: - return await send(ctx, "noTrackFound") + return await send_localized_message(ctx, "player.errors.noTrackFound") index = await player.add_track(tracks) - await send(ctx, "playlistLoad", attachment.filename, index) + await send_localized_message(ctx, "player.playback.playlistLoad", attachment.filename, index) except Exception as e: logger.error("error", exc_info=e) raise e @@ -569,16 +562,16 @@ class Basic(commands.Cog): "Display the players queue songs in your history queue." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_user_join(ctx.author): - return await send(ctx, "notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) + return await send_localized_message(ctx, "voice.connection.notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) if not player.queue.history(): return await nowplay(ctx, player) - view = ListView(player=player, author=ctx.author, is_queue=False) - view.response = await send(ctx, await view.build_embed(), view=view) + view = QueueView(player=player, author=ctx.author, is_queue=False) + view.response = await dispatch_message(ctx, await view.build_embed(), view=view) @commands.hybrid_command(name="leave", aliases=get_aliases("leave")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) @@ -586,19 +579,19 @@ class Basic(commands.Cog): "Disconnects the bot from your voice channel and chears the queue." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): if ctx.author in player.stop_votes: - return await send(ctx, "voted", ephemeral=True) + return await send_localized_message(ctx, "voting.voted", ephemeral=True) else: player.stop_votes.add(ctx.author) if len(player.stop_votes) >= (required := player.required(leave=True)): pass else: - return await send(ctx, "leaveVote", ctx.author, len(player.stop_votes), required) + return await send_localized_message(ctx, "player.controls.leave.vote", ctx.author, len(player.stop_votes), required) - await send(ctx, "left", ctx.author) + await send_localized_message(ctx, "player.controls.leave.success", ctx.author) await player.teardown() @commands.hybrid_command(name="nowplaying", aliases=get_aliases("nowplaying")) @@ -607,10 +600,10 @@ class Basic(commands.Cog): "Shows details of the current track." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_user_join(ctx.author): - return await send(ctx, "notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) + return await send_localized_message(ctx, "voice.connection.notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) await nowplay(ctx, player) @@ -618,20 +611,20 @@ class Basic(commands.Cog): @app_commands.describe(mode="Choose a looping mode.") @app_commands.choices(mode=[ app_commands.Choice(name=loop_type.name.title(), value=loop_type.name) - for loop_type in LoopType + for loop_type in voicelink.LoopType ]) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def loop(self, ctx: commands.Context, mode: str): "Changes Loop mode." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingModePerm", ephemeral=True) + return await send_localized_message(ctx, "permissions.missingMode", ephemeral=True) - await player.set_repeat(LoopType[mode] if mode in LoopType.__members__ else LoopType.OFF, ctx.author) - await send(ctx, "repeat", mode.capitalize()) + await player.set_repeat(voicelink.LoopType[mode] if mode in voicelink.LoopType.__members__ else voicelink.LoopType.OFF, ctx.author) + await send_localized_message(ctx, "player.controls.repeat", mode.capitalize()) @commands.hybrid_command(name="clear", aliases=get_aliases("clear")) @app_commands.describe(queue="Choose a queue that you want to clear.") @@ -644,13 +637,13 @@ class Basic(commands.Cog): "Remove all the tracks in your queue or history queue." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingQueuePerm", ephemeral=True) + return await send_localized_message(ctx, "permissions.missingQueue", ephemeral=True) await player.clear_queue(queue, ctx.author) - await send(ctx, "cleared", queue.capitalize()) + await send_localized_message(ctx, "queue.management.cleared", queue.capitalize()) @commands.hybrid_command(name="remove", aliases=get_aliases("remove")) @app_commands.describe( @@ -663,13 +656,13 @@ class Basic(commands.Cog): "Removes specified track or a range of tracks from the queue." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingQueuePerm", ephemeral=True) + return await send_localized_message(ctx, "permissions.missingQueue", ephemeral=True) removed_tracks = await player.remove_track(position1, position2, remove_target=member, requester=ctx.author) - await send(ctx, "removed", len(removed_tracks.keys())) + await send_localized_message(ctx, "queue.management.removed", len(removed_tracks.keys())) @commands.hybrid_command(name="forward", aliases=get_aliases("forward")) @app_commands.describe(position="Input an amount that you to forward to. Exmaple: 1:20") @@ -678,19 +671,19 @@ class Basic(commands.Cog): "Forwards by a certain amount of time in the current track. The default is 10 seconds." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPosPerm", ephemeral=True) + return await send_localized_message(ctx, "permissions.missingPosition", ephemeral=True) if not player.current: - return await send(ctx, "noTrackPlaying", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noTrackPlaying", ephemeral=True) - if not (num := format_time(position)): - return await send(ctx, "timeFormatError", ephemeral=True) + if not (num := format_to_ms(position)): + return await send_localized_message(ctx, "time.formatError", ephemeral=True) await player.seek(int(player.position + num)) - await send(ctx, "forward", ctime(player.position + num)) + await send_localized_message(ctx, "player.controls.forward", format_ms(player.position + num)) @commands.hybrid_command(name="rewind", aliases=get_aliases("rewind")) @app_commands.describe(position="Input an amount that you to rewind to. Exmaple: 1:20") @@ -699,19 +692,19 @@ class Basic(commands.Cog): "Rewind by a certain amount of time in the current track. The default is 10 seconds." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPosPerm", ephemeral=True) + return await send_localized_message(ctx, "permissions.missingPosition", ephemeral=True) if not player.current: - return await send(ctx, "noTrackPlaying", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noTrackPlaying", ephemeral=True) - if not (num := format_time(position)): - return await send(ctx, "timeFormatError", ephemeral=True) + if not (num := format_to_ms(position)): + return await send_localized_message(ctx, "time.formatError", ephemeral=True) await player.seek(int(player.position - num)) - await send(ctx, "rewind", ctime(player.position - num)) + await send_localized_message(ctx, "player.controls.rewind", format_ms(player.position - num)) @commands.hybrid_command(name="replay", aliases=get_aliases("replay")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) @@ -719,16 +712,16 @@ class Basic(commands.Cog): "Reset the progress of the current song." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPosPerm", ephemeral=True) + return await send_localized_message(ctx, "permissions.missingPosition", ephemeral=True) if not player.current: - return await send(ctx, "noTrackPlaying", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noTrackPlaying", ephemeral=True) await player.seek(0) - await send(ctx, "replay") + await send_localized_message(ctx, "player.controls.replay") @commands.hybrid_command(name="shuffle", aliases=get_aliases("shuffle")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) @@ -736,18 +729,18 @@ class Basic(commands.Cog): "Randomizes the tracks in the queue." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): if ctx.author in player.shuffle_votes: - return await send(ctx, "voted", ephemeral=True) + return await send_localized_message(ctx, "voting.voted", ephemeral=True) player.shuffle_votes.add(ctx.author) if len(player.shuffle_votes) < (required := player.required()): - return await send(ctx, "shuffleVote", ctx.author, len(player.shuffle_votes), required) + return await send_localized_message(ctx, "player.controls.shuffle.vote", ctx.author, len(player.shuffle_votes), required) await player.shuffle("queue", ctx.author) - await send(ctx, "shuffled") + await send_localized_message(ctx, "player.controls.shuffle.success") @commands.hybrid_command(name="swap", aliases=get_aliases("swap")) @app_commands.describe( @@ -759,13 +752,13 @@ class Basic(commands.Cog): "Swaps the specified song to the specified song." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPosPerm", ephemeral=True) + return await send_localized_message(ctx, "permissions.missingPosition", ephemeral=True) track1, track2 = await player.swap_track(position1, position2, ctx.author) - await send(ctx, "swapped", track1.title, track2.title) + await send_localized_message(ctx, "queue.management.swapped", track1.title, track2.title) @commands.hybrid_command(name="move", aliases=get_aliases("move")) @app_commands.describe( @@ -777,13 +770,13 @@ class Basic(commands.Cog): "Moves the specified song to the specified position." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingPosPerm", ephemeral=True) + return await send_localized_message(ctx, "permissions.missingPosition", ephemeral=True) moved_track = await player.move_track(target, to, ctx.author) - await send(ctx, "moved", moved_track, to) + await send_localized_message(ctx, "queue.management.moved", moved_track, to) @commands.hybrid_command(name="lyrics", aliases=get_aliases("lyrics")) @app_commands.describe(title="Searches for your query and displays the reutned lyrics.") @@ -793,20 +786,20 @@ class Basic(commands.Cog): if not title: player: voicelink.Player = ctx.guild.voice_client if not player or not player.is_playing: - return await send(ctx, "noTrackPlaying", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noTrackPlaying", ephemeral=True) title = player.current.title artist = player.current.author await ctx.defer() - lyrics_platform = LYRICS_PLATFORMS.get(settings.lyrics_platform) + lyrics_platform = voicelink.LYRICS_PLATFORMS.get(Config().lyrics_platform) if lyrics_platform: lyrics = await lyrics_platform().get_lyrics(title, artist) if not lyrics: - return await send(ctx, "lyricsNotFound", ephemeral=True) + return await send_localized_message(ctx, "lyrics.notFound", ephemeral=True) view = LyricsView(name=title, source={_: re.findall(r'.*\n(?:.*\n){,22}', v or "") for _, v in lyrics.items()}, author=ctx.author) - view.response = await send(ctx, view.build_embed(), view=view) + view.response = await dispatch_message(ctx, await view.build_embed(), view=view) @commands.hybrid_command(name="swapdj", aliases=get_aliases("swapdj")) @app_commands.describe(member="Choose a member to transfer the dj role.") @@ -815,22 +808,22 @@ class Basic(commands.Cog): "Transfer dj to another." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_user_join(ctx.author): - return await send(ctx, "notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) + return await send_localized_message(ctx, "voice.connection.notInChannel", ctx.author.mention, player.channel.mention, ephemeral=True) if player.dj.id != ctx.author.id or player.settings.get('dj', False): - return await send(ctx, "notdj", f"<@&{player.settings['dj']}>" if player.settings.get('dj') else player.dj.mention, ephemeral=True) + return await send_localized_message(ctx, "permissions.notDj", f"<@&{player.settings['dj']}>" if player.settings.get('dj') else player.dj.mention, ephemeral=True) if player.dj.id == member.id or member.bot: - return await send(ctx, "djToMe", ephemeral=True) + return await send_localized_message(ctx, "permissions.djToSelf", ephemeral=True) if member not in player.channel.members: - return await send(ctx, "djNotInChannel", member, ephemeral=True) + return await send_localized_message(ctx, "permissions.djNotInChannel", member, ephemeral=True) player.dj = member - await send(ctx, "djswap", member) + await send_localized_message(ctx, "permissions.djSwapped", member) @commands.hybrid_command(name="autoplay", aliases=get_aliases("autoplay")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) @@ -838,14 +831,14 @@ class Basic(commands.Cog): "Toggles autoplay mode, it will automatically queue the best songs to play." player: voicelink.Player = ctx.guild.voice_client if not player: - return await send(ctx, "noPlayer", ephemeral=True) + return await send_localized_message(ctx, "player.errors.noPlayer", ephemeral=True) if not player.is_privileged(ctx.author): - return await send(ctx, "missingAutoPlayPerm", ephemeral=True) + return await send_localized_message(ctx, "permissions.missingAutoPlay", ephemeral=True) check = not player.settings.get("autoplay", False) player.settings['autoplay'] = check - await send(ctx, "autoplay", await get_lang(ctx.guild.id, "enabled" if check else "disabled")) + await send_localized_message(ctx, "player.controls.autoplay", await LangHandler.get_lang(ctx.guild.id, "common.status.enabled" if check else "common.status.disabled")) if not player.is_playing: await player.do_next() @@ -862,7 +855,7 @@ class Basic(commands.Cog): category = "News" view = HelpView(self.bot, ctx.author) embed = view.build_embed(category) - view.response = await send(ctx, embed, view=view) + view.response = await dispatch_message(ctx, embed, view=view) @commands.hybrid_command(name="ping", aliases=get_aliases("ping")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) @@ -870,9 +863,9 @@ class Basic(commands.Cog): "Test if the bot is alive, and see the delay between your commands and my response." player: voicelink.Player = ctx.guild.voice_client - value = await get_lang(ctx.guild.id, "pingTitle1", "pingField1", "pingTitle2", "pingField2") + value = await LangHandler.get_lang(ctx.guild.id, "ping.title1", "ping.field1", "ping.title2", "ping.field2") - embed = discord.Embed(color=settings.embed_color) + embed = discord.Embed(color=Config().embed_color) embed.add_field( name=value[0], value=value[1].format( @@ -887,7 +880,7 @@ class Basic(commands.Cog): inline=False ) - await send(ctx, embed) + await dispatch_message(ctx, embed) async def setup(bot: commands.Bot) -> None: await bot.add_cog(Basic(bot)) \ No newline at end of file diff --git a/cogs/effect.py b/cogs/effect.py index 5953a9c..9055322 100644 --- a/cogs/effect.py +++ b/cogs/effect.py @@ -25,24 +25,24 @@ import discord import voicelink from function import ( - send, - get_lang, get_aliases, cooldown_check ) from discord import app_commands from discord.ext import commands +from voicelink import LangHandler +from voicelink.utils import send_localized_message async def check_access(ctx: commands.Context): player: voicelink.Player = ctx.guild.voice_client if not player: - text = await get_lang(ctx.guild.id, "noPlayer") + text = await LangHandler.get_lang(ctx.guild.id, "player.errors.noPlayer") raise voicelink.exceptions.VoicelinkException(text) if ctx.author not in player.channel.members: if not ctx.author.guild_permissions.manage_guild: - text = await get_lang(ctx.guild.id, "notInChannel") + text = await LangHandler.get_lang(ctx.guild.id, "voice.connection.notInChannel") raise voicelink.exceptions.VoicelinkException(text.format(ctx.author.mention, player.channel.mention)) return player @@ -72,7 +72,7 @@ class Effect(commands.Cog): effect = voicelink.Timescale(tag="speed", speed=value) await player.add_filter(effect, ctx.author) - await send(ctx, "addEffect", effect.tag) + await send_localized_message(ctx, "effects.added", effect.tag) @commands.hybrid_command(name="karaoke", aliases=get_aliases("karaoke")) @app_commands.describe( @@ -91,7 +91,7 @@ class Effect(commands.Cog): effect = voicelink.Karaoke(tag="karaoke", level=level, mono_level=monolevel, filter_band=filterband, filter_width=filterwidth) await player.add_filter(effect, ctx.author) - await send(ctx, "addEffect", effect.tag) + await send_localized_message(ctx, "effects.added", effect.tag) @commands.hybrid_command(name="tremolo", aliases=get_aliases("tremolo")) @app_commands.describe( @@ -108,7 +108,7 @@ class Effect(commands.Cog): effect = voicelink.Tremolo(tag="tremolo", frequency=frequency, depth=depth) await player.add_filter(effect, ctx.author) - await send(ctx, "addEffect", effect.tag) + await send_localized_message(ctx, "effects.added", effect.tag) @commands.hybrid_command(name="vibrato", aliases=get_aliases("vibrato")) @app_commands.describe( @@ -125,7 +125,7 @@ class Effect(commands.Cog): effect = voicelink.Vibrato(tag="vibrato", frequency=frequency, depth=depth) await player.add_filter(effect, ctx.author) - await send(ctx, "addEffect", effect.tag) + await send_localized_message(ctx, "effects.added", effect.tag) @commands.hybrid_command(name="rotation", aliases=get_aliases("rotation")) @app_commands.describe(hertz="The hertz of the rotation. Default is `0.2`") @@ -139,7 +139,7 @@ class Effect(commands.Cog): effect = voicelink.Rotation(tag="rotation", rotation_hertz=hertz) await player.add_filter(effect, ctx.author) - await send(ctx, "addEffect", effect.tag) + await send_localized_message(ctx, "effects.added", effect.tag) @commands.hybrid_command(name="distortion", aliases=get_aliases("distortion")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) @@ -152,7 +152,7 @@ class Effect(commands.Cog): effect = voicelink.Distortion(tag="distortion", sin_offset=0.0, sin_scale=1.0, cos_offset=0.0, cos_scale=1.0, tan_offset=0.0, tan_scale=1.0, offset=0.0, scale=1.0) await player.add_filter(effect, ctx.author) - await send(ctx, "addEffect", effect.tag) + await send_localized_message(ctx, "effects.added", effect.tag) @commands.hybrid_command(name="lowpass", aliases=get_aliases("lowpass")) @app_commands.describe(smoothing="The level of the lowPass. Default is `20.0`") @@ -166,7 +166,7 @@ class Effect(commands.Cog): effect = voicelink.LowPass(tag="lowpass", smoothing=smoothing) await player.add_filter(effect, ctx.author) - await send(ctx, "addEffect", effect.tag) + await send_localized_message(ctx, "effects.added", effect.tag) @commands.hybrid_command(name="channelmix", aliases=get_aliases("channelmix")) @app_commands.describe( @@ -185,7 +185,7 @@ class Effect(commands.Cog): effect = voicelink.ChannelMix(tag="channelmix", left_to_left=left_to_left, right_to_right=right_to_right, left_to_right=left_to_right, right_to_left=right_to_left) await player.add_filter(effect, ctx.author) - await send(ctx, "addEffect", effect.tag) + await send_localized_message(ctx, "effects.added", effect.tag) @commands.hybrid_command(name="nightcore", aliases=get_aliases("nightcore")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) @@ -195,7 +195,7 @@ class Effect(commands.Cog): effect = voicelink.Timescale.nightcore() await player.add_filter(effect, ctx.author) - await send(ctx, "addEffect", effect.tag) + await send_localized_message(ctx, "effects.added", effect.tag) @commands.hybrid_command(name="8d", aliases=get_aliases("8d")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) @@ -205,7 +205,7 @@ class Effect(commands.Cog): effect = voicelink.Rotation.nightD() await player.add_filter(effect, ctx.author) - await send(ctx, "addEffect", effect.tag) + await send_localized_message(ctx, "effects.added", effect.tag) @commands.hybrid_command(name="vaporwave", aliases=get_aliases("vaporwave")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) @@ -215,7 +215,7 @@ class Effect(commands.Cog): effect = voicelink.Timescale.vaporwave() await player.add_filter(effect, ctx.author) - await send(ctx, "addEffect", effect.tag) + await send_localized_message(ctx, "effects.added", effect.tag) @commands.hybrid_command(name="cleareffect", aliases=get_aliases("cleareffect")) @app_commands.describe(effect="Remove a specific sound effects.") @@ -230,7 +230,7 @@ class Effect(commands.Cog): else: await player.reset_filter() - await send(ctx, "clearEffect") + await send_localized_message(ctx, "effects.cleared") async def setup(bot: commands.Bot) -> None: await bot.add_cog(Effect(bot)) diff --git a/cogs/listeners.py b/cogs/listeners.py index f8a5583..44b2da3 100644 --- a/cogs/listeners.py +++ b/cogs/listeners.py @@ -29,6 +29,9 @@ import function as func from discord.ext import commands +from voicelink import MongoDBHandler, Config +from voicelink.utils import TempCtx + class Listeners(commands.Cog): """Music Cog.""" @@ -41,20 +44,16 @@ class Listeners(commands.Cog): async def start_nodes(self) -> None: """Connect and intiate nodes.""" - for n in func.settings.nodes.values(): + for n in Config().nodes.values(): try: - await self.voicelink.create_node( - bot=self.bot, - logger=func.logger, - **n - ) + await self.voicelink.create_node(bot=self.bot, **n) except Exception as e: func.logger.error(f'Node {n["identifier"]} is not able to connect! - Reason: {e}') async def restore_last_session_players(self) -> None: """Re-establish connections for players from the last session.""" await self.bot.wait_until_ready() - players = func.open_json(func.LAST_SESSION_FILE_NAME) + players = func.open_json(Config.LAST_SESSION_FILE_DIR) if not players: return @@ -75,11 +74,11 @@ class Listeners(commands.Cog): continue # Get the guild settings - settings = await func.get_settings(channel.guild.id) + settings = await MongoDBHandler.get_settings(channel.guild.id) # Connect to the channel and initialize the player. player: voicelink.Player = await channel.connect( - cls=voicelink.Player(self.bot, channel, func.TempCtx(dj_member, channel), settings) + cls=voicelink.Player(self.bot, channel, TempCtx(dj_member, channel), settings) ) # Restore the queue. @@ -89,7 +88,7 @@ class Listeners(commands.Cog): if not track_id: continue - decoded_track = voicelink.decode(track_id) + decoded_track = voicelink.Track.decode(track_id) requester = channel.guild.get_member(track_data.get("requester_id")) track = voicelink.Track(track_id=track_id, info=decoded_track, requester=requester) player.queue._queue.append(track) @@ -125,12 +124,11 @@ class Listeners(commands.Cog): # Delete the last session file if it exists. try: - file_path = os.path.join(func.ROOT_DIR, func.LAST_SESSION_FILE_NAME) - if os.path.exists(file_path): - os.remove(file_path) + if os.path.exists(Config.LAST_SESSION_FILE_DIR): + os.remove(Config.LAST_SESSION_FILE_DIR) except Exception as del_error: - func.logger.error("Failed to remove session file: %s", file_path, exc_info=del_error) + func.logger.error("Failed to remove session file: %s", Config.LAST_SESSION_FILE_DIR, exc_info=del_error) @commands.Cog.listener() async def on_voicelink_track_end(self, player: voicelink.Player, track, _): @@ -178,8 +176,8 @@ class Listeners(commands.Cog): if player.is_paused and len([m for m in player.channel.members if not m.bot]) == 1: await player.set_pause(False, member) - if self.bot.ipc._is_connected: - await self.bot.ipc.send({ + if player.is_ipc_connected: + await player._ipc_client.send({ "op": "updateGuild", "user": { "userId": str(member.id), diff --git a/cogs/playlist.py b/cogs/playlist.py index a7f3f6b..948d700 100644 --- a/cogs/playlist.py +++ b/cogs/playlist.py @@ -21,71 +21,173 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import discord, voicelink, time +import time +import discord +import voicelink from io import StringIO from discord import app_commands from discord.ext import commands from function import ( - send, - time as ctime, - get_user, - update_user, - check_roles, - get_lang, - settings, get_aliases, cooldown_check, logger ) -from views import PlaylistView, InboxView, HelpView +from voicelink import MongoDBHandler, Config +from voicelink.views import PlaylistViewManager, InboxView, HelpView +from voicelink.utils import format_ms, dispatch_message, send_localized_message def assign_playlist_id(existed: list) -> str: for i in range(200, 210): if str(i) not in existed: return str(i) -async def check_playlist_perms(user_id: int, author_id: int, d_id: str) -> dict: - playlist = await get_user(author_id, 'playlist') - playlist = playlist.get(d_id) +async def check_playlist_perms(user_id: int, author_id: int, playlist_id: str) -> dict: + """Check if user has read permissions for a specific playlist.""" + user_data = await MongoDBHandler.get_user(author_id, d_type='playlist') + playlist = user_data.get(playlist_id) + if not playlist or user_id not in playlist['perms']['read']: return {} + return playlist async def check_playlist(ctx: commands.Context, name: str = None, full: bool = False, share: bool = True) -> dict: - user = await get_user(ctx.author.id, 'playlist') + """Get user's playlist data with various filtering options.""" + user_playlists = await MongoDBHandler.get_user(ctx.author.id, d_type='playlist') - await ctx.defer() + if isinstance(ctx, discord.Interaction) and not ctx.interaction.response.is_done(): + await ctx.defer() + if full: - return user + return user_playlists if not name: - return {'playlist': user['200'], 'position': 1, 'id': "200"} - - for index, data in enumerate(user, start=1): - playlist = user[data] - if playlist['name'].lower() == name: + return {'playlist': user_playlists['200'], 'position': 1, 'id': "200"} + + for index, playlist_id in enumerate(user_playlists, start=1): + playlist = user_playlists[playlist_id] + + if playlist['name'].lower() == name.lower(): if playlist['type'] == 'share' and share: - playlist = await check_playlist_perms(ctx.author.id, playlist['user'], playlist['referId']) - if not playlist or ctx.author.id not in playlist['perms']['read']: - return {'playlist': None, 'position': index, 'id': data} - return {'playlist': playlist, 'position': index, 'id': data} + shared_playlist = await check_playlist_perms(ctx.author.id, playlist['user'], playlist['referId']) + + if not shared_playlist or ctx.author.id not in shared_playlist['perms']['read']: + return {'playlist': None, 'position': index, 'id': playlist_id} + + return {'playlist': shared_playlist, 'position': index, 'id': playlist_id} + + return {'playlist': playlist, 'position': index, 'id': playlist_id} + return {'playlist': None, 'position': None, 'id': None} async def search_playlist(url: str, requester: discord.Member, time_needed: bool = True) -> dict: + """Search for playlist tracks from a URL.""" try: tracks = await voicelink.NodePool.get_node().get_tracks(url, requester=requester) - tracks = {"name": tracks.name, "tracks": tracks.tracks} + result = {"name": tracks.name, "tracks": tracks.tracks} + if time_needed: - time = sum([track.length for track in tracks["tracks"]]) - except: + result["time"] = format_ms(sum(track.length for track in tracks.tracks)) + + return result + except Exception: return {} - - if time_needed: - tracks["time"] = ctime(time) - return tracks +async def _process_playlist(ctx: commands.Context, playlist_data: dict, playlist_id: str, is_locked: bool): + """Process a single playlist and return its formatted data.""" + playlist_type = playlist_data['type'] + + # Get appropriate emoji + if is_locked: + emoji = '๐Ÿ”’' + elif playlist_type == 'link': + emoji = '๐ŸŒ' + elif playlist_type == 'share': + emoji = '๐Ÿค' + else: + emoji = 'โค๏ธ' + + # Handle link playlist + if playlist_type == 'link': + tracks = await search_playlist(playlist_data['uri'], requester=ctx.author) + if not tracks: + return None + + return { + 'emoji': emoji, + 'id': playlist_id, + 'time': tracks['time'], + 'name': playlist_data['name'], + 'tracks': tracks['tracks'], + 'perms': playlist_data['perms'], + 'type': playlist_data['type'] + } + + # Handle shared playlist + if playlist_type == 'share': + shared_playlist = await check_playlist_perms( + ctx.author.id, + playlist_data['user'], + playlist_data['referId'] + ) + + if not shared_playlist: + await MongoDBHandler.update_user(ctx.author.id, {"$unset": {f"playlist.{playlist_id}": 1}}) + return None + + if shared_playlist['type'] == 'link': + tracks = await search_playlist(shared_playlist['uri'], requester=ctx.author) + if not tracks: + return None + + return { + 'emoji': emoji, + 'id': playlist_id, + 'time': tracks['time'], + 'name': playlist_data['name'], + 'tracks': tracks['tracks'], + 'perms': shared_playlist['perms'], + 'owner': playlist_data['user'], + 'type': 'share' + } + + decoded_tracks = [] + total_time = 0 + for track in shared_playlist['tracks']: + decoded_track = voicelink.Track.decode(track) + total_time += decoded_track.get("length", 0) + decoded_tracks.append(decoded_track) + + return { + 'emoji': emoji, + 'id': playlist_id, + 'time': format_ms(total_time), + 'name': playlist_data['name'], + 'tracks': decoded_tracks, + 'perms': shared_playlist['perms'], + 'owner': playlist_data['user'], + 'type': 'share' + } + + decoded_tracks = [] + total_time = 0 + for track in playlist_data['tracks']: + decoded_track = voicelink.Track.decode(track) + total_time += decoded_track.get("length", 0) + decoded_tracks.append(decoded_track) + + return { + 'emoji': emoji, + 'id': playlist_id, + 'time': format_ms(total_time), + 'name': playlist_data['name'], + 'tracks': decoded_tracks, + 'perms': playlist_data['perms'], + 'owner': playlist_data.get('owner', ctx.author.id), + 'type': playlist_data['type'] + } class Playlists(commands.Cog, name="playlist"): def __init__(self, bot: commands.Bot) -> None: @@ -93,7 +195,7 @@ class Playlists(commands.Cog, name="playlist"): self.description = "This is the Vocard playlist system. You can save your favorites and use Vocard to play on any server." async def playlist_autocomplete(self, interaction: discord.Interaction, current: str) -> list: - playlists_raw: dict[str, dict] = await get_user(interaction.user.id, 'playlist') + playlists_raw: dict[str, dict] = await MongoDBHandler.get_user(interaction.user.id, d_type='playlist') playlists = [value['name'] for value in playlists_raw.values()] if playlists_raw else [] if current: return [app_commands.Choice(name=p, value=p) for p in playlists if current in p] @@ -107,7 +209,7 @@ class Playlists(commands.Cog, name="playlist"): async def playlist(self, ctx: commands.Context): view = HelpView(self.bot, ctx.author) embed = view.build_embed(self.qualified_name) - view.response = send(ctx, embed, view=view) + view.response = dispatch_message(ctx, embed, view=view) @playlist.command(name="play", aliases=get_aliases("play")) @app_commands.describe( @@ -121,10 +223,10 @@ class Playlists(commands.Cog, name="playlist"): result = await check_playlist(ctx, name.lower() if name else None) if not result['playlist']: - return await send(ctx, 'playlistNotFound', name, ephemeral=True) - rank, max_p, max_t = check_roles() + return await send_localized_message(ctx, 'playlist.errors.notFound', name, ephemeral=True) + max_p, max_t, _ = Config().get_playlist_config() if result['position'] > max_p: - return await send(ctx, 'playlistNotAccess', ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.noAccess', ephemeral=True) player: voicelink.Player = ctx.guild.voice_client if not player: @@ -134,21 +236,21 @@ class Playlists(commands.Cog, name="playlist"): tracks = await search_playlist(result['playlist']['uri'], ctx.author, time_needed=False) else: if not result['playlist']['tracks']: - return await send(ctx, 'playlistNoTrack', result['playlist']['name'], ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.noTrack', result['playlist']['name'], ephemeral=True) _tracks = [] for track in result['playlist']['tracks'][:max_t]: - _tracks.append(voicelink.Track(track_id=track, info=voicelink.decode(track), requester=ctx.author)) + _tracks.append(voicelink.Track(track_id=track, info=voicelink.Track.decode(track), requester=ctx.author)) tracks = {"name": result['playlist']['name'], "tracks": _tracks} if not tracks: - return await send(ctx, 'playlistNoTrack', result['playlist']['name'], ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.noTrack', result['playlist']['name'], ephemeral=True) if value and 0 < value <= (len(tracks['tracks'])): tracks['tracks'] = [tracks['tracks'][value - 1]] await player.add_track(tracks['tracks']) - await send(ctx, 'playlistPlay', result['playlist']['name'], len(tracks['tracks'][:max_t])) + await send_localized_message(ctx, 'playlist.actions.play', result['playlist']['name'], len(tracks['tracks'][:max_t])) if not player.is_playing: await player.do_next() @@ -156,63 +258,32 @@ class Playlists(commands.Cog, name="playlist"): @playlist.command(name="view", aliases=get_aliases("view")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def view(self, ctx: commands.Context) -> None: - "List all your playlist and all songs in your favourite playlist." - user = await check_playlist(ctx, full=True) - rank, max_p, max_t = check_roles() - - results = [] - for index, data in enumerate(user, start=1): - playlist = user[data] - time = 0 - try: - if playlist['type'] == 'link': - tracks = await search_playlist(playlist['uri'], requester=ctx.author) - results.append({'emoji': ('๐Ÿ”’' if max_p < index else '๐Ÿ”—'), 'id': data, 'time': tracks['time'], 'name': playlist['name'], 'tracks': tracks['tracks'], 'perms': playlist['perms'], 'type': playlist['type']}) - - else: - if share := playlist['type'] == 'share': - playlist = await check_playlist_perms(ctx.author.id, playlist['user'], playlist['referId']) - if not playlist: - await update_user(ctx.author.id, {"$unset": {f"playlist.{data}": 1}}) - continue - - if playlist['type'] == 'link': - tracks = await search_playlist(playlist['uri'], requester=ctx.author) - results.append({'emoji': ('๐Ÿ”’' if max_p < index else '๐Ÿค'), 'id': data, 'time': tracks['time'], 'name': user[data]['name'], 'tracks': tracks['tracks'], 'perms': playlist['perms'], 'owner': user[data]['user'], 'type': 'share'}) - continue - - init = [] - for track in playlist['tracks']: - dt = voicelink.decode(track) - time += dt.get("length", 0) - init.append(dt) - playlist['tracks'] = init - results.append({'emoji': ('๐Ÿ”’' if max_p < index else ('๐Ÿค' if share else 'โค๏ธ')), 'id': data, 'time': ctime(time), 'name': user[data]['name'], 'tracks': playlist['tracks'], 'perms': playlist['perms'], 'owner': user[data].get('user', None), 'type': user[data]['type']}) + """List all your playlists and all songs in your favourite playlist.""" + user_playlists = await check_playlist(ctx, full=True) + max_p, _, _ = Config().get_playlist_config() - except: - results.append({'emoji': 'โ›”', 'id': data, 'time': '--:--', 'name': 'Error', 'tracks': [], 'type': 'error'}) - - text = await get_lang(ctx.guild.id, "playlistViewTitle", "playlistViewHeaders", "playlistFooter") - embed = discord.Embed( - title=text[0].format(ctx.author.display_name), - description='```prolog\n %4s %10s %12s %10s\n' % tuple(text[1].split(",")), - color=settings.embed_color - ) + playlist_results = [] - for index in range(max_p): - try: - info = results[index] - track_info = (info['emoji'], info['id'], f"[{info['time']}]", info['name'], f"{len(info['tracks'])}") - except IndexError: - track_info = ("๐ŸŽต", "-"*3, "[--:--]", "-"*6, f"-") - - embed.description += '%0s %3s. %10s %12s %10s\n' % track_info + for index, playlist_id in enumerate(user_playlists, start=1): + playlist_data = user_playlists[playlist_id] + is_locked = max_p < index - embed.description += "```" - embed.set_footer(text=text[2]) - - view = PlaylistView(embed, results, ctx.author) - view.response = await send(ctx, embed, view=view, ephemeral=True) + try: + result = await _process_playlist(ctx, playlist_data, playlist_id, is_locked) + if result: + playlist_results.append(result) + except Exception: + playlist_results.append({ + 'emoji': 'โ›”', + 'id': playlist_id, + 'time': '--:--', + 'name': 'Error', + 'tracks': [], + 'type': 'error' + }) + + view = PlaylistViewManager(ctx, playlist_results) + view.response = await dispatch_message(ctx, content=view.build_embed(), view=view, ephemeral=True) @playlist.command(name="create", aliases=get_aliases("create")) @app_commands.describe( @@ -223,25 +294,25 @@ class Playlists(commands.Cog, name="playlist"): async def create(self, ctx: commands.Context, name: str, link: str = None): "Create your custom playlist." if len(name) > 10: - return await send(ctx, 'playlistOverText', ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.nameOverLimit', ephemeral=True) - rank, max_p, max_t = check_roles() + max_p, _, _ = Config().get_playlist_config() user = await check_playlist(ctx, full=True) if len(user) >= max_p: - return await send(ctx, 'overPlaylistCreation', max_p, ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.limitReached', max_p, ephemeral=True) for data in user: if user[data]['name'].lower() == name.lower(): - return await send(ctx, 'playlistExists', name, ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.exists', name, ephemeral=True) if link: tracks = await voicelink.NodePool.get_node().get_tracks(link, requester=ctx.author) if not isinstance(tracks, voicelink.Playlist): - return await send(ctx, "playlistNotInvalidUrl", ephemeral=True) + return await send_localized_message(ctx, "playlist.errors.invalidUrl", ephemeral=True) data = {'uri': link, 'perms': {'read': []}, 'name': name, 'type': 'link'} if link else {'tracks': [], 'perms': {'read': [], 'write': [], 'remove': []}, 'name': name, 'type': 'playlist'} - await update_user(ctx.author.id, {"$set": {f"playlist.{assign_playlist_id([data for data in user])}": data}}) - await send(ctx, "playlistCreated", name) + await MongoDBHandler.update_user(ctx.author.id, {"$set": {f"playlist.{assign_playlist_id([data for data in user])}": data}}) + await send_localized_message(ctx, "playlist.actions.create", name) @playlist.command(name="delete", aliases=get_aliases("delete")) @app_commands.describe(name="The name of the playlist.") @@ -251,15 +322,15 @@ class Playlists(commands.Cog, name="playlist"): "Delete your custom playlist." result = await check_playlist(ctx, name.lower(), share=False) if not result['playlist']: - return await ctx(ctx, "playlistNotFound", name, ephemeral=True) + return await send_localized_message(ctx, "playlist.errors.notFound", name, ephemeral=True) if result['id'] == "200": - return await send(ctx, "playlistDeleteError", ephemeral=True) + return await send_localized_message(ctx, "playlist.errors.deleteDefault", ephemeral=True) if result['playlist']['type'] == 'share': - await update_user(result['playlist']['user'], {"$pull": {f"playlist.{result['playlist']['referId']}.perms.read": ctx.author.id}}) + await MongoDBHandler.update_user(result['playlist']['user'], {"$pull": {f"playlist.{result['playlist']['referId']}.perms.read": ctx.author.id}}) - await update_user(ctx.author.id, {"$unset": {f"playlist.{result['id']}": 1}}) - return await send(ctx, "playlistRemove", result["playlist"]["name"]) + await MongoDBHandler.update_user(ctx.author.id, {"$unset": {f"playlist.{result['id']}": 1}}) + return await send_localized_message(ctx, "playlist.actions.remove", result["playlist"]["name"]) @playlist.command(name="share", aliases=get_aliases("share")) @app_commands.describe( @@ -271,28 +342,28 @@ class Playlists(commands.Cog, name="playlist"): async def share(self, ctx: commands.Context, member: discord.Member, name: str): "Share your custom playlist with your friends." if member.id == ctx.author.id: - return await send(ctx, 'playlistSendErrorPlayer', ephemeral=True) + return await send_localized_message(ctx, 'playlist.sharing.sendErrorPlayer', ephemeral=True) if member.bot: - return await send(ctx, 'playlistSendErrorBot', ephemeral=True) + return await send_localized_message(ctx, 'playlist.sharing.sendErrorBot', ephemeral=True) result = await check_playlist(ctx, name.lower(), share=False) if not result['playlist']: - return await send(ctx, 'playlistNotFound', name, ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.notFound', name, ephemeral=True) if result['playlist']['type'] == 'share': - return await send(ctx, 'playlistBelongs', result['playlist']['user'], ephemeral=True) + return await send_localized_message(ctx, 'playlist.sharing.belongs', result['playlist']['user'], ephemeral=True) if member.id in result['playlist']['perms']['read']: - return await send(ctx, 'playlistShare', member, ephemeral=True) + return await send_localized_message(ctx, 'playlist.sharing.alreadyShared', member, ephemeral=True) - receiver = await get_user(member.id) + receiver = await MongoDBHandler.get_user(member.id) if not receiver: - return await send(ctx, 'noPlaylistAcc', member) + return await send_localized_message(ctx, 'playlist.sharing.noAccount', member) for mail in receiver['inbox']: if mail['sender'] == ctx.author.id and mail['referId'] == result['id']: - return await send(ctx, 'playlistSent', ephemeral=True) + return await send_localized_message(ctx, 'playlist.sharing.alreadySent', ephemeral=True) if len(receiver['inbox']) >= 10: - return await send(ctx.guild.id, 'inboxFull', member, ephemeral=True) + return await send_localized_message(ctx, 'playlist.inbox.full', member, ephemeral=True) - await update_user( + await MongoDBHandler.update_user( member.id, {"$push": {"inbox": { 'sender': ctx.author.id, @@ -303,7 +374,7 @@ class Playlists(commands.Cog, name="playlist"): 'type': 'invite' }}} ) - return await send(ctx, "invitationSent", member) + return await send_localized_message(ctx, "playlist.sharing.invitationSent", member) @playlist.command(name="rename", aliases=get_aliases("rename")) @app_commands.describe( @@ -315,36 +386,36 @@ class Playlists(commands.Cog, name="playlist"): async def rename(self, ctx: commands.Context, name: str, newname: str) -> None: "Rename your custom playlist." if len(newname) > 10: - return await send(ctx, 'playlistOverText', ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.nameOverLimit', ephemeral=True) if name.lower() == newname.lower(): - return await send(ctx, 'playlistSameName', ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.sameName', ephemeral=True) user = await check_playlist(ctx, full=True) found, id = False, 0 for data in user: if user[data]['name'].lower() == name.lower(): found, id = True, data if user[data]['name'].lower() == newname.lower(): - return await send(ctx, 'playlistExists', ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.exists', ephemeral=True) if not found: - return await send(ctx.guild.id, 'playlistNotFound', name, ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.notFound', name, ephemeral=True) - await update_user(ctx.author.id, {"$set": {f'playlist.{id}.name': newname}}) - await send(ctx, 'playlistRenamed', name, newname) + await MongoDBHandler.update_user(ctx.author.id, {"$set": {f'playlist.{id}.name': newname}}) + await send_localized_message(ctx, 'playlist.actions.renamed', name, newname) @playlist.command(name="inbox", aliases=get_aliases("inbox")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def inbox(self, ctx: commands.Context) -> None: "Show your playlist invitation." - user = await get_user(ctx.author.id) - rank, max_p, max_t = check_roles() + user = await MongoDBHandler.get_user(ctx.author.id) + max_p, _, _ = Config().get_playlist_config() if not user['inbox']: - return await send(ctx, 'inboxNoMsg', ephemeral=True) + return await send_localized_message(ctx, "playlist.inbox.noMessages", ephemeral=True) inbox = user['inbox'].copy() view = InboxView(ctx.author, user['inbox']) - view.response = await send(ctx, view.build_embed(), view=view, ephemeral=True) + view.response = await dispatch_message(ctx, view.build_embed(), view=view, ephemeral=True) await view.wait() if inbox == user['inbox']: @@ -353,7 +424,7 @@ class Playlists(commands.Cog, name="playlist"): update_data, dId = {}, {dId for dId in user["playlist"]} for data in view.new_playlist[:(max_p - len(user['playlist']))]: addId = assign_playlist_id(dId) - await update_user(data['sender'], {"$push": {f"playlist.{data['referId']}.perms.read": ctx.author.id}}) + await MongoDBHandler.update_user(data['sender'], {"$push": {f"playlist.{data['referId']}.perms.read": ctx.author.id}}) update_data[f'playlist.{addId}'] = { 'user': data['sender'], 'referId': data['referId'], 'name': f"Share{time.strftime('%M%S', time.gmtime(int(data['time'])))}", @@ -363,7 +434,7 @@ class Playlists(commands.Cog, name="playlist"): dId.add(addId) if update_data: - await update_user(ctx.author.id, {"$set": update_data}) + await MongoDBHandler.update_user(ctx.author.id, {"$set": update_data}) @playlist.command(name="add", aliases=get_aliases("add")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) @@ -376,26 +447,26 @@ class Playlists(commands.Cog, name="playlist"): "Add tracks in to your custom playlist." result = await check_playlist(ctx, name.lower(), share=False) if not result['playlist']: - return await send(ctx, 'playlistNotFound', name, ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.notFound', name, ephemeral=True) if result['playlist']['type'] in ['share', 'link']: - return await send(ctx, 'playlistNotAllow', ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.notAllowed', ephemeral=True) - rank, max_p, max_t = check_roles() + _, max_t, _ = Config().get_playlist_config() if len(result['playlist']['tracks']) >= max_t: - return await send(ctx, 'playlistLimitTrack', max_t, ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.trackLimitReached', max_t, ephemeral=True) results = await voicelink.NodePool.get_node().get_tracks(query, requester=ctx.author) if not results: - return await send(ctx, 'noTrackFound') + return await send_localized_message(ctx, 'player.errors.noTrackFound') if isinstance(results, voicelink.Playlist): - return await send(ctx, 'playlistPlaylistLink', ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.playlistLinkNotAllowed', ephemeral=True) if results[0].is_stream: - return await send(ctx, 'playlistStream', ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.streamNotAllowed', ephemeral=True) - await update_user(ctx.author.id, {"$push": {f'playlist.{result["id"]}.tracks': results[0].track_id}}) - await send(ctx, 'playlistAdded', results[0].title, ctx.author, result['playlist']['name']) + await MongoDBHandler.update_user(ctx.author.id, {"$push": {f'playlist.{result["id"]}.tracks': results[0].track_id}}) + await send_localized_message(ctx, 'playlist.actions.trackAdded', results[0].title, ctx.author, result['playlist']['name']) @playlist.command(name="remove", aliases=get_aliases("remove")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) @@ -408,16 +479,16 @@ class Playlists(commands.Cog, name="playlist"): "Remove song from your favorite playlist." result = await check_playlist(ctx, name.lower(), share=False) if not result['playlist']: - return await send(ctx, 'playlistNotFound', name, ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.notFound', name, ephemeral=True) if result['playlist']['type'] in ['link', 'share']: - return await send(ctx, 'playlistNotAllow', ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.notAllowed', ephemeral=True) if not 0 < position <= len(result['playlist']['tracks']): - return await send(ctx, 'playlistPositionNotFound', position, name) + return await send_localized_message(ctx, 'playlist.errors.positionNotFound', position, name) - await update_user(ctx.author.id, {"$pull": {f'playlist.{result["id"]}.tracks': result['playlist']['tracks'][position - 1]}}) + await MongoDBHandler.update_user(ctx.author.id, {"$pull": {f'playlist.{result["id"]}.tracks': result['playlist']['tracks'][position - 1]}}) - track = voicelink.decode(result['playlist']['tracks'][position - 1]) - await send(ctx, 'playlistRemoved', track.get("title"), ctx.author, name) + track = voicelink.Track.decode(result['playlist']['tracks'][position - 1]) + await send_localized_message(ctx, 'playlist.actions.trackRemoved', track.get("title"), ctx.author, name) @playlist.command(name="clear", aliases=get_aliases("clear")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) @@ -426,13 +497,13 @@ class Playlists(commands.Cog, name="playlist"): "Remove all songs from your favorite playlist." result = await check_playlist(ctx, name.lower(), share=False) if not result['playlist']: - return await send(ctx, 'playlistNotFound', name, ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.notFound', name, ephemeral=True) if result['playlist']['type'] in ['link', 'share']: - return await send(ctx, 'playlistNotAllow', ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.notAllowed', ephemeral=True) - await update_user(ctx.author.id, {"$set": {f'playlist.{result["id"]}.tracks': []}}) - await send(ctx, 'playlistClear', name) + await MongoDBHandler.update_user(ctx.author.id, {"$set": {f'playlist.{result["id"]}.tracks': []}}) + await send_localized_message(ctx, 'playlist.actions.cleared', name) @playlist.command(name="export", aliases=get_aliases("export")) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) @@ -441,29 +512,29 @@ class Playlists(commands.Cog, name="playlist"): "Exports the entire playlist to a text file" result = await check_playlist(ctx, name.lower()) if not result['playlist']: - return await send(ctx, 'playlistNotFound', name, ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.notFound', name, ephemeral=True) if result['playlist']['type'] == 'link': tracks = await search_playlist(result['playlist']['uri'], ctx.author, time_needed=False) else: if not result['playlist']['tracks']: - return await send(ctx, 'playlistNoTrack', result['playlist']['name'], ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.noTrack', result['playlist']['name'], ephemeral=True) _tracks = [] for track in result['playlist']['tracks']: - _tracks.append(voicelink.Track(track_id=track, info=voicelink.decode(track), requester=ctx.author)) + _tracks.append(voicelink.Track(track_id=track, info=voicelink.Track.decode(track), requester=ctx.author)) tracks = {"name": result['playlist']['name'], "tracks": _tracks} if not tracks: - return await send(ctx, 'playlistNoTrack', result['playlist']['name'], ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.noTrack', result['playlist']['name'], ephemeral=True) temp = "" raw = "----------->Raw Info<-----------\n" total_length = 0 for index, track in enumerate(tracks['tracks'], start=1): - temp += f"{index}. {track.title} [{ctime(track.length)}]\n" + temp += f"{index}. {track.title} [{format_ms(track.length)}]\n" raw += track.track_id if index != len(tracks['tracks']): raw += "," @@ -472,7 +543,7 @@ class Playlists(commands.Cog, name="playlist"): temp = "!Remember do not change this file!\n------------->Info<-------------\nPlaylist: {} ({})\nRequester: {} ({})\nTracks: {} - {}\n------------>Tracks<------------\n".format( tracks['name'], result['playlist']['type'], ctx.author.display_name, ctx.author.id, - len(tracks['tracks']), ctime(total_length) + len(tracks['tracks']), format_ms(total_length) ) + temp temp += raw @@ -484,17 +555,17 @@ class Playlists(commands.Cog, name="playlist"): async def _import(self, ctx: commands.Context, name: str, attachment: discord.Attachment): "Create your custom playlist." if len(name) > 10: - return await send(ctx, 'playlistOverText', ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.nameOverLimit', ephemeral=True) - rank, max_p, max_t = check_roles() + max_p, _, _ = Config().get_playlist_config() user = await check_playlist(ctx, full=True) if len(user) >= max_p: - return await send(ctx, 'overPlaylistCreation', max_p, ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.limitReached', max_p, ephemeral=True) for data in user: if user[data]['name'].lower() == name.lower(): - return await send(ctx, 'playlistExists', name, ephemeral=True) + return await send_localized_message(ctx, 'playlist.errors.exists', name, ephemeral=True) try: bytes = await attachment.read() @@ -502,8 +573,8 @@ class Playlists(commands.Cog, name="playlist"): track_ids = track_ids.decode().split(",") data = {'tracks': track_ids, 'perms': {'read': [], 'write': [], 'remove': []}, 'name': name, 'type': 'playlist'} - await update_user(ctx.author.id, {"$set": {f"playlist.{assign_playlist_id([data for data in user])}": data}}) - await send(ctx, 'playlistCreated', name) + await MongoDBHandler.update_user(ctx.author.id, {"$set": {f"playlist.{assign_playlist_id([data for data in user])}": data}}) + await send_localized_message(ctx, 'playlist.actions.create', name) except Exception as e: logger.error("Decode Error", exc_info=e) diff --git a/cogs/settings.py b/cogs/settings.py index b3834fd..2be80b5 100644 --- a/cogs/settings.py +++ b/cogs/settings.py @@ -29,18 +29,14 @@ import function as func from discord import app_commands from discord.ext import commands from function import ( - LANGS, - send, - update_settings, - get_settings, - get_lang, - time as ctime, get_aliases, - cooldown_check, - format_bytes + cooldown_check ) -from views import DebugView, HelpView, EmbedBuilderView +from voicelink import MongoDBHandler, LangHandler +from voicelink.views import DebugView, HelpView, EmbedBuilderView +from voicelink.placeholders import PlayerPlaceholder +from voicelink.utils import format_ms, format_bytes, dispatch_message, send_localized_message def status_icon(status: bool) -> str: return "โœ…" if status else "โŒ" @@ -58,7 +54,7 @@ class Settings(commands.Cog, name="settings"): async def settings(self, ctx: commands.Context): view = HelpView(self.bot, ctx.author) embed = view.build_embed(self.qualified_name) - view.response = await send(ctx, embed, view=view) + view.response = await dispatch_message(ctx, embed, view=view) @settings.command(name="prefix", aliases=get_aliases("prefix")) @commands.has_permissions(manage_guild=True) @@ -66,10 +62,10 @@ class Settings(commands.Cog, name="settings"): async def prefix(self, ctx: commands.Context, prefix: str): "Change the default prefix for message commands." if not self.bot.intents.message_content: - return await send(ctx, "missingIntents", "MESSAGE_CONTENT", ephemeral=True) + return await send_localized_message(ctx, "common.error.missingIntents", "MESSAGE_CONTENT", ephemeral=True) - await update_settings(ctx.guild.id, {"$set": {"prefix": prefix}}) - await send(ctx, "setPrefix", prefix, prefix) + await MongoDBHandler.update_settings(ctx.guild.id, {"$set": {"prefix": prefix}}) + await send_localized_message(ctx, "settings.actions.prefixSet", prefix, prefix) @settings.command(name="language", aliases=get_aliases("language")) @commands.has_permissions(manage_guild=True) @@ -77,25 +73,25 @@ class Settings(commands.Cog, name="settings"): async def language(self, ctx: commands.Context, language: str): "You can choose your preferred language, the bot message will change to the language you set." language = language.upper() - if language not in LANGS: - return await send(ctx, "languageNotFound") + if language not in voicelink.LangHandler.get_all_languages(): + return await send_localized_message(ctx, "settings.actions.languageNotFound") - await update_settings(ctx.guild.id, {"$set": {'lang': language}}) - await send(ctx, 'changedLanguage', language) + await MongoDBHandler.update_settings(ctx.guild.id, {"$set": {'lang': language}}) + await send_localized_message(ctx, 'settings.actions.languageChanged', language) @language.autocomplete('language') async def autocomplete_callback(self, interaction: discord.Interaction, current: str) -> list: if current: - return [app_commands.Choice(name=lang, value=lang) for lang in LANGS.keys() if current.upper() in lang] - return [app_commands.Choice(name=lang, value=lang) for lang in LANGS.keys()] + return [app_commands.Choice(name=lang, value=lang) for lang in voicelink.LangHandler.get_all_languages() if current.upper() in lang] + return [app_commands.Choice(name=lang, value=lang) for lang in voicelink.LangHandler.get_all_languages()] @settings.command(name="dj", aliases=get_aliases("dj")) @commands.has_permissions(manage_guild=True) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def dj(self, ctx: commands.Context, role: discord.Role = None): "Set a DJ role or remove DJ role." - await update_settings(ctx.guild.id, {"$set": {'dj': role.id}} if role else {"$unset": {'dj': None}}) - await send(ctx, 'setDJ', f"<@&{role.id}>" if role else "None") + await MongoDBHandler.update_settings(ctx.guild.id, {"$set": {'dj': role.id}} if role else {"$unset": {'dj': None}}) + await send_localized_message(ctx, 'settings.actions.djSet', f"<@&{role.id}>" if role else "None") @settings.command(name="queue", aliases=get_aliases("queue")) @app_commands.choices(mode=[ @@ -107,57 +103,57 @@ class Settings(commands.Cog, name="settings"): async def queue(self, ctx: commands.Context, mode: str): "Change to another type of queue mode." mode = mode if mode.lower() in voicelink.queue.QUEUE_TYPES else next(iter(voicelink.queue.QUEUE_TYPES)) - await update_settings(ctx.guild.id, {"$set": {"queue_type": mode}}) - await send(ctx, "setQueue", mode.capitalize()) + await MongoDBHandler.update_settings(ctx.guild.id, {"$set": {"queue_type": mode}}) + await send_localized_message(ctx, "settings.actions.queueModeSet", mode.capitalize()) @settings.command(name="247", aliases=get_aliases("247")) @commands.has_permissions(manage_guild=True) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def playforever(self, ctx: commands.Context): "Toggles 24/7 mode, which disables automatic inactivity-based disconnects." - settings = await get_settings(ctx.guild.id) + settings = await MongoDBHandler.get_settings(ctx.guild.id) toggle = settings.get('24/7', False) - await update_settings(ctx.guild.id, {"$set": {'24/7': not toggle}}) - await send(ctx, '247', await get_lang(ctx.guild.id, "enabled" if not toggle else "disabled")) + await MongoDBHandler.update_settings(ctx.guild.id, {"$set": {'24/7': not toggle}}) + await send_localized_message(ctx, 'settings.actions.mode247', await LangHandler.get_lang(ctx.guild.id, "common.status.enabled" if not toggle else "common.status.disabled")) @settings.command(name="bypassvote", aliases=get_aliases("bypassvote")) @commands.has_permissions(manage_guild=True) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def bypassvote(self, ctx: commands.Context): "Toggles voting system." - settings = await get_settings(ctx.guild.id) - toggle = settings.get('disabled_vote', True) - await update_settings(ctx.guild.id, {"$set": {'disabled_vote': not toggle}}) - await send(ctx, 'bypassVote', await get_lang(ctx.guild.id, "enabled" if not toggle else "disabled")) + settings = await MongoDBHandler.get_settings(ctx.guild.id) + toggle = settings.get('common.status.disabled_vote', True) + await MongoDBHandler.update_settings(ctx.guild.id, {"$set": {'common.status.disabled_vote': not toggle}}) + await send_localized_message(ctx, 'settings.actions.bypassVote', await LangHandler.get_lang(ctx.guild.id, "common.status.enabled" if not toggle else "common.status.disabled")) @settings.command(name="view", aliases=get_aliases("view")) @commands.has_permissions(manage_guild=True) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def view(self, ctx: commands.Context): "Show all the bot settings in your server." - settings = await get_settings(ctx.guild.id) + settings = await MongoDBHandler.get_settings(ctx.guild.id) - texts = await get_lang(ctx.guild.id, "settingsMenu", "settingsTitle", "settingsValue", "settingsTitle2", "settingsValue2", "settingsTitle3", "settingsPermTitle", "settingsPermValue") - embed = discord.Embed(color=func.settings.embed_color) + texts = await LangHandler.get_lang(ctx.guild.id, "settings.menu", "settings.basic.title", "settings.basic.value", "settings.queue.title", "settings.queue.value", "settings.voice.title", "settings.permissions.title", "settings.permissions.value") + embed = discord.Embed(color=voicelink.Config().embed_color) embed.set_author(name=texts[0].format(ctx.guild.name), icon_url=self.bot.user.display_avatar.url) if ctx.guild.icon: embed.set_thumbnail(url=ctx.guild.icon.url) dj_role = ctx.guild.get_role(settings.get('dj', 0)) embed.add_field(name=texts[1], value=texts[2].format( - settings.get('prefix', func.settings.bot_prefix) or 'None', + settings.get('prefix', voicelink.Config().bot_prefix) or 'None', settings.get('lang', 'EN'), settings.get('controller', True), dj_role.name if dj_role else 'None', - settings.get('disabled_vote', False), + settings.get('common.status.disabled_vote', False), settings.get('24/7', False), settings.get('volume', 100), - ctime(settings.get('played_time', 0) * 60 * 1000), + format_ms(settings.get('played_time', 0) * 60 * 1000), inline=True) ) embed.add_field(name=texts[3], value=texts[4].format( settings.get("queue_type", "Queue"), - func.settings.max_queue, + voicelink.Config().max_queue, settings.get("duplicate_track", True) )) @@ -173,7 +169,7 @@ class Settings(commands.Cog, name="settings"): ), inline=False ) - await send(ctx, embed) + await dispatch_message(ctx, embed) @settings.command(name="volume", aliases=get_aliases("volume")) @app_commands.describe(value="Input a integer.") @@ -185,15 +181,15 @@ class Settings(commands.Cog, name="settings"): if player: await player.set_volume(value, ctx.author) - await update_settings(ctx.guild.id, {"$set": {'volume': value}}) - await send(ctx, 'setVolume', value) + await MongoDBHandler.update_settings(ctx.guild.id, {"$set": {'volume': value}}) + await send_localized_message(ctx, 'settings.actions.volumeSet', value) @settings.command(name="togglecontroller", aliases=get_aliases("togglecontroller")) @commands.has_permissions(manage_guild=True) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def togglecontroller(self, ctx: commands.Context): "Toggles the music controller." - settings = await get_settings(ctx.guild.id) + settings = await MongoDBHandler.get_settings(ctx.guild.id) toggle = not settings.get('controller', True) player: voicelink.Player = ctx.guild.voice_client @@ -203,63 +199,64 @@ class Settings(commands.Cog, name="settings"): except: discord.ui.View.from_message(player.controller).stop() - await update_settings(ctx.guild.id, {"$set": {'controller': toggle}}) - await send(ctx, 'toggleController', await get_lang(ctx.guild.id, "enabled" if toggle else "disabled")) + await MongoDBHandler.update_settings(ctx.guild.id, {"$set": {'controller': toggle}}) + await send_localized_message(ctx, 'settings.actions.controllerToggled', await LangHandler.get_lang(ctx.guild.id, "common.status.enabled" if toggle else "common.status.disabled")) @settings.command(name="duplicatetrack", aliases=get_aliases("duplicatetrack")) @commands.has_permissions(manage_guild=True) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def duplicatetrack(self, ctx: commands.Context): "Toggle Vocard to prevent duplicate songs from queuing." - settings = await get_settings(ctx.guild.id) + settings = await MongoDBHandler.get_settings(ctx.guild.id) toggle = not settings.get('duplicate_track', False) player: voicelink.Player = ctx.guild.voice_client if player: player.queue._allow_duplicate = toggle - await update_settings(ctx.guild.id, {"$set": {'duplicate_track': toggle}}) - return await send(ctx, "toggleDuplicateTrack", await get_lang(ctx.guild.id, "disabled" if toggle else "enabled")) + await MongoDBHandler.update_settings(ctx.guild.id, {"$set": {'duplicate_track': toggle}}) + return await send_localized_message(ctx, "settings.actions.duplicateTrackToggled", await LangHandler.get_lang(ctx.guild.id, "common.status.disabled" if toggle else "common.status.enabled")) @settings.command(name="customcontroller", aliases=get_aliases("customcontroller")) @commands.has_permissions(manage_guild=True) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def customcontroller(self, ctx: commands.Context): "Customizes music controller embeds." - settings = await get_settings(ctx.guild.id) - controller_settings = settings.get("default_controller", func.settings.controller) + settings = await MongoDBHandler.get_settings(ctx.guild.id) + controller_settings = settings.get("default_controller", voicelink.Config().controller) - view = EmbedBuilderView(ctx, controller_settings.get("embeds").copy()) - view.response = await send(ctx, view.build_embed(), view=view) + placeholder = PlayerPlaceholder(ctx.bot) + view = EmbedBuilderView(ctx, placeholder, controller_settings.get("embeds").copy()) + view.response = await dispatch_message(ctx, view.build_embed(), view=view) @settings.command(name="controllermsg", aliases=get_aliases("controllermsg")) @commands.has_permissions(manage_guild=True) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def controllermsg(self, ctx: commands.Context): "Toggles to send a message when clicking the button in the music controller." - settings = await get_settings(ctx.guild.id) + settings = await MongoDBHandler.get_settings(ctx.guild.id) toggle = not settings.get('controller_msg', True) - await update_settings(ctx.guild.id, {"$set": {'controller_msg': toggle}}) - await send(ctx, 'toggleControllerMsg', await get_lang(ctx.guild.id, "enabled" if toggle else "disabled")) + await MongoDBHandler.update_settings(ctx.guild.id, {"$set": {'controller_msg': toggle}}) + await send_localized_message(ctx, 'settings.actions.controllerMsgToggled', await LangHandler.get_lang(ctx.guild.id, "common.status.enabled" if toggle else "common.status.disabled")) @settings.command(name="silentmsg", aliases=get_aliases("silentmsg")) @commands.has_permissions(manage_guild=True) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def silentmsg(self, ctx: commands.Context): "Toggle silent messaging to send discreet messages without alerting recipients." - settings = await get_settings(ctx.guild.id) + settings = await MongoDBHandler.get_settings(ctx.guild.id) toggle = not settings.get('silent_msg', False) - await update_settings(ctx.guild.id, {"$set": {'silent_msg': toggle}}) - await send(ctx, 'toggleSilentMsg', await get_lang(ctx.guild.id, "enabled" if toggle else "disabled")) + await MongoDBHandler.update_settings(ctx.guild.id, {"$set": {'silent_msg': toggle}}) + await send_localized_message(ctx, 'settings.actions.silentMsgToggled', await LangHandler.get_lang(ctx.guild.id, "common.status.enabled" if toggle else "common.status.disabled")) @settings.command(name="stageannounce", aliases=get_aliases("stageannounce")) @commands.has_permissions(manage_guild=True) @commands.dynamic_cooldown(cooldown_check, commands.BucketType.guild) async def stageannounce(self, ctx: commands.Context, template: str = None): "Customize the channel topic template" - await update_settings(ctx.guild.id, {"$set": {'stage_announce_template': template}}) - await send(ctx, "setStageAnnounceTemplate") + await MongoDBHandler.update_settings(ctx.guild.id, {"$set": {'stage_announce_template': template}}) + await send_localized_message(ctx, "voice.stageChannel.setAnnounceTemplate") @settings.command(name="setupchannel", aliases=get_aliases("setupchannel")) @app_commands.describe( @@ -270,7 +267,7 @@ class Settings(commands.Cog, name="settings"): async def setupchannel(self, ctx: commands.Context, channel: discord.TextChannel = None) -> None: "Sets up a dedicated channel for song requests in your server." if not self.bot.intents.message_content: - return await send(ctx, "missingIntents", "MESSAGE_CONTENT", ephemeral=True) + return await send_localized_message(ctx, "common.error.missingIntents", "MESSAGE_CONTENT", ephemeral=True) if not channel: try: @@ -282,25 +279,25 @@ class Settings(commands.Cog, name="settings"): } channel = await ctx.guild.create_text_channel("vocard-song-requests", overwrites=overwrites) except: - return await send(ctx, "noCreatePermission") + return await send_localized_message(ctx, "permissions.noCreatePermission") channel_perms = channel.permissions_for(ctx.me) if not channel_perms.text() and not channel_perms.manage_messages: - return await send(ctx, "noCreatePermission") + return await send_localized_message(ctx, "permissions.noCreatePermission") - settings = await func.get_settings(ctx.guild.id) - controller = settings.get("default_controller", func.settings.controller).get("embeds", {}).get("inactive", {}) - message = await channel.send(embed=voicelink.build_embed(controller, voicelink.Placeholders(self.bot))) + settings = await MongoDBHandler.get_settings(ctx.guild.id) + controller = settings.get("default_controller", voicelink.Config().controller).get("embeds", {}).get("inactive", {}) + message = await channel.send(embed=PlayerPlaceholder.build_embed(controller, PlayerPlaceholder(self.bot))) - await update_settings(ctx.guild.id, {"$set": {'music_request_channel': { + await MongoDBHandler.update_settings(ctx.guild.id, {"$set": {'music_request_channel': { "text_channel_id": channel.id, "controller_msg_id": message.id, }}}) - await send(ctx, "createSongRequestChannel", channel.mention) + await send_localized_message(ctx, "songRequest.channelCreated", channel.mention) @app_commands.command(name="debug") async def debug(self, interaction: discord.Interaction): - if interaction.user.id not in func.settings.bot_access_user: + if interaction.user.id not in voicelink.Config().bot_access_user: return await interaction.response.send_message("You are not able to use this command!") memory = psutil.virtual_memory() @@ -308,7 +305,7 @@ class Settings(commands.Cog, name="settings"): available_memory, total_memory = memory.available, memory.total used_disk_space, total_disk_space = disk.used, disk.total - embed = discord.Embed(title="๐Ÿ“„ Debug Panel", color=func.settings.embed_color) + embed = discord.Embed(title="๐Ÿ“„ Debug Panel", color=voicelink.Config().embed_color) embed.description = "```== System Info ==\n" \ f"โ€ข CPU: {psutil.cpu_freq().current}Mhz ({psutil.cpu_percent()}%)\n" \ f"โ€ข RAM: {format_bytes(total_memory - available_memory)}/{format_bytes(total_memory, True)} ({memory.percent}%)\n" \ @@ -316,7 +313,7 @@ class Settings(commands.Cog, name="settings"): embed.add_field( name="๐Ÿค– Bot Information", - value=f"```โ€ข VERSION: {func.settings.version}\n" \ + value=f"```โ€ข VERSION: {voicelink.Config().version}\n" \ f"โ€ข LATENCY: {self.bot.latency:.2f}ms\n" \ f"โ€ข GUILDS: {len(self.bot.guilds)}\n" \ f"โ€ข USERS: {sum([guild.member_count or 0 for guild in self.bot.guilds])}\n" \ @@ -335,7 +332,7 @@ class Settings(commands.Cog, name="settings"): f"โ€ข CPU: {node.stats.cpu_process_load:.1f}%\n" \ f"โ€ข RAM: {format_bytes(node.stats.free)}/{format_bytes(total_memory, True)} ({(node.stats.free/total_memory) * 100:.1f}%)\n" f"โ€ข LATENCY: {node.latency:.2f}ms\n" \ - f"โ€ข UPTIME: {func.time(node.stats.uptime)}```" + f"โ€ข UPTIME: {format_ms(node.stats.uptime)}```" ) else: embed.add_field( diff --git a/cogs/task.py b/cogs/task.py index fe31273..fd0f030 100644 --- a/cogs/task.py +++ b/cogs/task.py @@ -26,7 +26,6 @@ import discord import function as func from discord.ext import commands, tasks -from addons import Placeholders class Task(commands.Cog): def __init__(self, bot: commands.Bot): @@ -36,7 +35,7 @@ class Task(commands.Cog): self.cache_cleaner.start() self.current_act = 0 - self.placeholder = Placeholders(bot) + self.placeholder = voicelink.BotPlaceholder(bot) def cog_unload(self): self.activity_update.cancel() @@ -48,7 +47,7 @@ class Task(commands.Cog): await self.bot.wait_until_ready() try: - act_data = func.settings.activity[(self.current_act + 1) % len(func.settings.activity) - 1] + act_data = voicelink.Config().activity[(self.current_act + 1) % len(voicelink.Config().activity) - 1] act_original = self.bot.activity act_type = getattr(discord.ActivityType, act_data.get("type", "").lower(), discord.ActivityType.playing) act_name = self.placeholder.replace(act_data.get("name", "")) @@ -58,7 +57,7 @@ class Task(commands.Cog): if act_original.type != act_type or act_original.name != act_name: self.bot.activity = discord.Activity(type=act_type, name=act_name) await self.bot.change_presence(activity=self.bot.activity, status=status_type) - self.current_act = (self.current_act + 1) % len(func.settings.activity) + self.current_act = (self.current_act + 1) % len(voicelink.Config().activity) func.logger.info(f"Changed the bot status to {act_name}") @@ -104,8 +103,7 @@ class Task(commands.Cog): @tasks.loop(hours=12.0) async def cache_cleaner(self): - func.SETTINGS_BUFFER.clear() - func.USERS_BUFFER.clear() + await voicelink.MongoDBHandler.cleanup_cache() async def setup(bot: commands.Bot): await bot.add_cog(Task(bot)) \ No newline at end of file diff --git a/function.py b/function.py index 7f4444c..6c2ae98 100644 --- a/function.py +++ b/function.py @@ -21,68 +21,21 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import discord, json, os, copy, logging +import json +import os +import logging +import voicelink from discord.ext import commands -from time import strptime -from addons import Settings - -from typing import ( - Optional, - Union, - Dict, - Any -) - -from motor.motor_asyncio import ( - AsyncIOMotorClient, - AsyncIOMotorCollection, -) +from typing import Optional ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) +logger: logging.Logger = logging.getLogger("vocard") + if not os.path.exists(os.path.join(ROOT_DIR, "settings.json")): raise Exception("Settings file not set!") -#--------------- Cache Var --------------- -settings: Settings -logger: logging.Logger = logging.getLogger("vocard") - -MONGO_DB: AsyncIOMotorClient -SETTINGS_DB: AsyncIOMotorCollection -USERS_DB: AsyncIOMotorCollection - -LANGS: dict[str, dict[str, str]] = {} #Stores all the languages in ./langs -LOCAL_LANGS: dict[str, dict[str, str]] = {} #Stores all the localization languages in ./local_langs -SETTINGS_BUFFER: dict[int, dict[str, Any]] = {} #Cache guild language -USERS_BUFFER: dict[str, dict] = {} - -MISSING_TRANSLATOR: dict[str, list[str]] = {} - -USER_BASE: dict[str, Any] = { - 'playlist': { - '200': { - 'tracks':[], - 'perms': {'read': [], 'write':[], 'remove': []}, - 'name':'Favourite', - 'type':'playlist' - } - }, - 'history': [], - 'inbox':[] -} - -ALLOWED_MENTIONS = discord.AllowedMentions().none() -LAST_SESSION_FILE_NAME = "last-session.json" - -#-------------- Vocard Classes -------------- -class TempCtx(): - def __init__(self, author: discord.Member, channel: discord.VoiceChannel) -> None: - self.author: discord.Member = author - self.channel: discord.VoiceChannel = channel - self.guild: discord.Guild = channel.guild - -#-------------- Vocard Functions -------------- def open_json(path: str) -> dict: try: with open(os.path.join(ROOT_DIR, path), encoding="utf8") as json_file: @@ -100,227 +53,13 @@ def update_json(path: str, new_data: dict) -> None: with open(os.path.join(ROOT_DIR, path), "w") as json_file: json.dump(data, json_file, indent=4) -def langs_setup() -> None: - for language in os.listdir(os.path.join(ROOT_DIR, "langs")): - if language.endswith('.json'): - LANGS[language[:-5]] = {} - - for language in os.listdir(os.path.join(ROOT_DIR, "local_langs")): - if language.endswith('.json'): - LOCAL_LANGS[language[:-5]] = open_json(os.path.join("local_langs", language)) - - return - -def time(millis: int) -> str: - seconds = (millis // 1000) % 60 - minutes = (millis // (1000 * 60)) % 60 - hours = (millis // (1000 * 60 * 60)) % 24 - days = millis // (1000 * 60 * 60 * 24) - - if days > 0: - return "%d days, %02d:%02d:%02d" % (days, hours, minutes, seconds) - elif hours > 0: - return "%d:%02d:%02d" % (hours, minutes, seconds) - else: - return "%02d:%02d" % (minutes, seconds) - -def format_time(number:str) -> int: - try: - try: - num = strptime(number, '%M:%S') - except ValueError: - try: - num = strptime(number, '%S') - except ValueError: - num = strptime(number, '%H:%M:%S') - except: - return 0 - - return (int(num.tm_hour) * 3600 + int(num.tm_min) * 60 + int(num.tm_sec)) * 1000 - -def get_source(source: str, type: str) -> str: - source_settings: dict[str, str] = settings.sources_settings.get(source.lower().replace(" ", ""), settings.sources_settings.get("others")) - return source_settings.get(type) - def cooldown_check(ctx: commands.Context) -> Optional[commands.Cooldown]: - if ctx.author.id in settings.bot_access_user: + if ctx.author.id in voicelink.Config().bot_access_user: return None - cooldown = settings.cooldowns_settings.get(f"{ctx.command.parent.qualified_name} {ctx.command.name}" if ctx.command.parent else ctx.command.name) + cooldown = voicelink.Config().cooldowns_settings.get(f"{ctx.command.parent.qualified_name} {ctx.command.name}" if ctx.command.parent else ctx.command.name) if not cooldown: return None return commands.Cooldown(cooldown[0], cooldown[1]) def get_aliases(name: str) -> list: - return settings.aliases_settings.get(name, []) - -def check_roles() -> tuple[str, int, int]: - return 'Normal', 5, 500 - -def truncate_string(text: str, length: int = 40) -> str: - return text[:length - 3] + "..." if len(text) > length else text - - -def format_bytes(bytes: int, unit: bool = False): - if bytes <= 1_000_000_000: - return f"{bytes / (1024 ** 2):.1f}" + ("MB" if unit else "") - - else: - return f"{bytes / (1024 ** 3):.1f}" + ("GB" if unit else "") - -def _get_lang(lang: str, *keys) -> Optional[Union[list[str], str]]: - if lang not in LANGS: - lang = "EN" - - if not LANGS[lang]: - LANGS[lang] = open_json(os.path.join("langs", f"{lang}.json")) - - lang_dict = LANGS[lang] - - if len(keys) == 1: - return lang_dict.get(keys[0], "Not found!") - return [lang_dict.get(key, "Not found!") for key in keys] - -async def get_lang(guild_id:int, *keys) -> Optional[Union[list[str], str]]: - settings = await get_settings(guild_id) - return _get_lang(settings.get("lang"), *keys) - -async def send( - ctx: Union[commands.Context, discord.Interaction], - content: Union[str, discord.Embed] = None, - *params, - view: discord.ui.View = None, - delete_after: float = None, - ephemeral: bool = False, - requires_fetch: bool = False -) -> Optional[discord.Message]: - if content is None: - content = "No content provided." - - # Determine the text to send - if isinstance(content, discord.Embed): - embed = content - text = None - else: - text = await get_lang(ctx.guild.id, content) - if text: - text = text.format(*params) - else: - text = content.format(*params) - embed = None - - # Determine the sending function - send_func = ( - ctx.send if isinstance(ctx, commands.Context) else - ctx.channel.send if isinstance(ctx, TempCtx) else - ctx.followup.send if ctx.response.is_done() else - ctx.response.send_message - ) - - # Check settings for delete_after duration - settings = await get_settings(ctx.guild.id) - send_kwargs = { - "content": text, - "embed": embed, - "allowed_mentions": ALLOWED_MENTIONS, - "silent": settings.get("silent_msg", False), - } - - if "delete_after" in send_func.__code__.co_varnames: - if settings and ctx.channel.id == settings.get("music_request_channel", {}).get("text_channel_id"): - delete_after = 10 - send_kwargs["delete_after"] = delete_after - - if "ephemeral" in send_func.__code__.co_varnames: - send_kwargs["ephemeral"] = ephemeral - - if view: - send_kwargs["view"] = view - - # Send the message or embed - message = await send_func(**send_kwargs) - - if isinstance(message, discord.InteractionCallbackResponse): - message = message.resource - - if requires_fetch and isinstance(message, (discord.WebhookMessage, discord.InteractionMessage)): - message = await message.fetch() - - return message - -async def update_db(db: AsyncIOMotorCollection, tempStore: dict, filter: dict, data: dict) -> bool: - for mode, action in data.items(): - for key, value in action.items(): - cursors = key.split(".") - - nested_data = tempStore - for c in cursors[:-1]: - nested_data = nested_data.setdefault(c, {}) - - if mode == "$set": - try: - nested_data[cursors[-1]] = value - except TypeError: - nested_data[int(cursors[-1])] = value - - elif mode == "$unset": - nested_data.pop(cursors[-1], None) - - elif mode == "$inc": - nested_data[cursors[-1]] = nested_data.get(cursors[-1], 0) + value - - elif mode == "$push": - if isinstance(value, dict) and "$each" in value: - nested_data.setdefault(cursors[-1], []).extend(value["$each"][value.get("$slice", len(value["$each"])):]) - else: - nested_data.setdefault(cursors[-1], []).extend([value]) - - elif mode == "$push": - if isinstance(value, dict) and "$each" in value: - nested_data.setdefault(cursors[-1], []).extend(value["$each"]) - nested_data[cursors[-1]] = nested_data[cursors[-1]][value.get("$slice", len(value["$each"])):] - else: - nested_data.setdefault(cursors[-1], []).extend([value]) - - elif mode == "$pull": - if cursors[-1] in nested_data: - value = value.get("$in", []) if isinstance(value, dict) else [value] - nested_data[cursors[-1]] = [item for item in nested_data[cursors[-1]] if item not in value] - - else: - return False - - result = await db.update_one(filter, data) - return result.modified_count > 0 - -async def get_settings(guild_id:int) -> dict[str, Any]: - settings = SETTINGS_BUFFER.get(guild_id, None) - if not settings: - settings = await SETTINGS_DB.find_one({"_id": guild_id}) - if not settings: - await SETTINGS_DB.insert_one({"_id": guild_id}) - - settings = SETTINGS_BUFFER[guild_id] = settings or {} - return settings - -async def update_settings(guild_id: int, data: dict[str, dict[str, Any]]) -> bool: - settings = await get_settings(guild_id) - return await update_db(SETTINGS_DB, settings, {"_id": guild_id}, data) - -async def get_user(user_id: int, d_type: Optional[str] = None, need_copy: bool = True) -> Dict[str, Any]: - user = USERS_BUFFER.get(user_id) - if not user: - user = await USERS_DB.find_one({"_id": user_id}) - if not user: - user = {"_id": user_id, **USER_BASE} - await USERS_DB.insert_one(user) - - USERS_BUFFER[user_id] = user - - if d_type: - user = user.setdefault(d_type, copy.deepcopy(USER_BASE.get(d_type))) - - return copy.deepcopy(user) if need_copy else user - -async def update_user(user_id:int, data:dict) -> bool: - playlist = await get_user(user_id, need_copy=False) - return await update_db(USERS_DB, playlist, {"_id": user_id}, data) \ No newline at end of file + return voicelink.Config().aliases_settings.get(name, []) \ No newline at end of file diff --git a/langs/CH.json b/langs/CH.json deleted file mode 100644 index 29b890a..0000000 --- a/langs/CH.json +++ /dev/null @@ -1,172 +0,0 @@ -{ - "unknownException": "โš ๏ธ ๅŸท่กŒๅ‘ฝไปคๆ™‚ๅ‡บ็พๅ•้กŒ๏ผ่ซ‹็จๅพŒๅ†่ฉฆ๏ผŒๆˆ–ๅŠ ๅ…ฅๆˆ‘ๅ€‘็š„ Discord ไผบๆœๅ™จ็ฒๅพ—้€ฒไธ€ๆญฅๆ”ฏๆดใ€‚", - "enabled": "ๅทฒๅ•Ÿ็”จ", - "disabled": "ๅทฒๅœ็”จ", - "nodeReconnect": "่ซ‹็จๅพŒๅ†่ฉฆ๏ผๅœจ็ฏ€้ปž้‡ๆ–ฐ้€ฃๆŽฅๅพŒๅ†ๅ˜—่ฉฆใ€‚", - "noChannel": "ๆฒ’ๆœ‰่ชž้Ÿณ้ ป้“ๅฏไพ›้€ฃๆŽฅใ€‚่ซ‹ๆไพ›ไธ€ๅ€‹่ชž้Ÿณ้ ป้“ๆˆ–ๅŠ ๅ…ฅไธ€ๅ€‹่ชž้Ÿณ้ ป้“ใ€‚", - "alreadyConnected": "ๅทฒ็ถ“้€ฃๆŽฅๅˆฐ่ชž้Ÿณ้ ป้“ใ€‚", - "noPermission": "ๆŠฑๆญ‰๏ผๆˆ‘ๆฒ’ๆœ‰ๆฌŠ้™ๅŠ ๅ…ฅๆˆ–ๅœจๆ‚จ็š„่ชž้Ÿณ้ ป้“ไธญ็™ผ่จ€ใ€‚", - "noCreatePermission": "ๆŠฑๆญ‰๏ผๆˆ‘ๆฒ’ๆœ‰ๆฌŠ้™ๅปบ็ซ‹ๆญŒๆ›ฒ่ซ‹ๆฑ‚้ ป้“ใ€‚", - "noPlaySource": "ๆ‰พไธๅˆฐไปปไฝ•ๅฏๆ’ญๆ”พ็š„ไพ†ๆบ๏ผ", - "noPlayer": "ๅœจๆญคไผบๆœๅ™จไธŠๆ‰พไธๅˆฐๆ’ญๆ”พๅ™จใ€‚", - "notVote": "ๆญคๅ‘ฝไปค้œ€่ฆๆ‚จ็š„ๆŠ•็ฅจ๏ผ่ผธๅ…ฅ `/vote` ไปฅ็ฒๅ–ๆ›ดๅคš่ณ‡่จŠใ€‚", - "missingIntents": "ๆŠฑๆญ‰๏ผŒๆญคๅ‘ฝไปค็„กๆณ•ๅŸท่กŒ๏ผŒๅ› ็‚บๆฉŸๅ™จไบบ็ผบๅฐ‘ๆ‰€้œ€็š„่ซ‹ๆฑ‚ๆ„ๅœ–๏ผš`({0})`.", - "languageNotFound": "ๆ‰พไธๅˆฐ่ชž่จ€ๅŒ…๏ผ่ซ‹้ธๆ“‡ไธ€ๅ€‹็พๆœ‰็š„่ชž่จ€ๅŒ…ใ€‚", - "changedLanguage": "ๅทฒๆˆๅŠŸๅˆ‡ๆ›ๅˆฐ `{0}` ่ชž่จ€ๅŒ…ใ€‚", - "setPrefix": "ๅฎŒๆˆ๏ผๆˆ‘็š„ๅ‰็ถดๅœจๆ‚จ็š„ไผบๆœๅ™จไธญ็พๅœจๆ˜ฏ `{0}`ใ€‚ๅ˜—่ฉฆ้‹่กŒ `{1}ping` ไพ†ๆธฌ่ฉฆๅฎƒใ€‚", - "setDJ": "ๅทฒๅฐ‡ DJ ่จญ็ฝฎ็‚บ {0}ใ€‚", - "setQueue": "ๅทฒๅฐ‡้šŠๅˆ—ๆจกๅผ่จญ็ฝฎ็‚บ `{0}`ใ€‚", - "247": "็พๅœจๆ‚จๆœ‰ `{0}` 24/7 ๆจกๅผใ€‚", - "bypassVote": "็พๅœจๆ‚จๆœ‰ `{0}` ๆŠ•็ฅจ็ณป็ตฑใ€‚", - "setVolume": "ๅทฒๅฐ‡้Ÿณ้‡่จญ็ฝฎ็‚บ `{0}`%", - "toggleController": "็พๅœจๆ‚จๅทฒ `{0}` ้Ÿณๆจ‚ๆŽงๅˆถๅ™จใ€‚", - "toggleDuplicateTrack": "็พๅœจๆ‚จๅทฒ `{0}` ้˜ฒๆญข้šŠๅˆ—ไธญๅญ˜ๅœจ้‡่ค‡ๆ›ฒ็›ฎใ€‚", - "toggleControllerMsg": "็พๅœจๆ‚จๅทฒๅพž้Ÿณๆจ‚ๆŽงๅˆถๅ™จ `{0}` ๆถˆๆฏใ€‚", - "toggleSilentMsg": "ไฝ {0}้œ้ป˜ๆถˆๆฏใ€‚", - "settingsMenu": "ไผบๆœๅ™จ่จญ็ฝฎ | {0}", - "settingsTitle": "โค๏ธ ๅŸบๆœฌ่ณ‡่จŠ๏ผš", - "settingsValue": "```ๅ‰็ถด๏ผš{0}\n่ชž่จ€๏ผš{1}\n้Ÿณๆจ‚ๆŽงๅˆถๅ™จ๏ผš{2}\nDJ ่ง’่‰ฒ๏ผš@{3}\nๆŠ•็ฅจ็นž้Ž๏ผš{4}\n24/7๏ผš{5}\n้ป˜่ช้Ÿณ้‡๏ผš{6}%\nๆ’ญๆ”พๆ™‚้–“๏ผš{7}```", - "settingsTitle2": "๐Ÿ”— ้šŠๅˆ—่ณ‡่จŠ๏ผš", - "settingsValue2": "```้šŠๅˆ—ๆจกๅผ๏ผš{0}\nๆœ€ๅคงๆญŒๆ›ฒๆ•ธ๏ผš{1}\nๅ…่จฑ้‡่ค‡ๆ›ฒ็›ฎ๏ผš{2}```", - "settingsTitle3": "๐ŸŽค ่ชž้Ÿณ็‹€ๆ…‹่ณ‡่จŠ๏ผš", - "settingsPermTitle": "โœจ ๆฌŠ้™๏ผš", - "settingsPermValue": "```{0} ็ฎก็†ๅ“ก\n{1} ็ฎก็†ไผบๆœๅ™จ\n{2} ็ฎก็†้ ป้“\n{3} ็ฎก็†่จŠๆฏ```", - "pingTitle1": "ๆฉŸๅ™จไบบ่ณ‡่จŠ๏ผš", - "pingTitle2": "ๆ’ญๆ”พๅ™จไฟกๆฏ๏ผš", - "pingField1": "```ๅˆ†็‰‡ ID: {0}/{1}\nๅˆ†็‰‡ๅปถ้ฒ: {2:.3f}s {3}\nๅ€ๅŸŸ: {4}```", - "pingField2": "```็ฏ€้ปž: {0} - {1:.3f}s\nๆ’ญๆ”พๅ™จๆ•ธ้‡: {2}\n่ชž้Ÿณๅ€ๅŸŸ: {3}```", - "addEffect": "ๅฅ—็”จ้Ÿณๆ•ˆ`{0}`ๆฟพ้กใ€‚", - "clearEffect": "่ฒ้Ÿณๆ•ˆๆžœๅทฒๆธ…้™ค๏ผ", - "filterTagAlreadyInUse": "ๆญค่ฒ้Ÿณๆ•ˆๆžœๅทฒๅœจไฝฟ็”จไธญ๏ผ่ซ‹ไฝฟ็”จ /cleareffect ็งป้™คๅฎƒใ€‚", - "playlistViewTitle": "๐Ÿ“œ ๆ‰€ๆœ‰ {0} ็š„ๆ’ญๆ”พๆธ…ๅ–ฎ", - "playlistViewHeaders": "ID:,ๆ™‚้–“:,ๅ็จฑ:,ๆ›ฒ็›ฎๆ•ธ:", - "playlistFooter": "่ผธๅ…ฅ /playlist play [ๆ’ญๆ”พๆธ…ๅ–ฎ] ๅŠ ๅ…ฅๆญคๆ’ญๆ”พๆธ…ๅ–ฎ่‡ณ้šŠๅˆ—ไธญใ€‚", - "playlistNotFound": "ๆ‰พไธๅˆฐๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`]ใ€‚่ผธๅ…ฅ /playlist view ๆŸฅ็œ‹ๆ‰€ๆœ‰ๆ’ญๆ”พๆธ…ๅ–ฎใ€‚", - "playlistNotAccess": "ๆŠฑๆญ‰๏ผไฝ ็„กๆฌŠ่จชๅ•ๆญคๆ’ญๆ”พๆธ…ๅ–ฎ๏ผ", - "playlistNoTrack": "ๆŠฑๆญ‰๏ผๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`] ไธญๆฒ’ๆœ‰ๆ›ฒ็›ฎใ€‚", - "playlistNotAllow": "ๆญคๅ‘ฝไปคไธๅ…่จฑไฝฟ็”จๆ–ผๅทฒ้€ฃ็ตๆˆ–ๅ…ฑไบซ็š„ๆ’ญๆ”พๆธ…ๅ–ฎใ€‚", - "playlistPlay": "ๅทฒๅŠ ๅ…ฅๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`] ็š„ `{1}` ้ฆ–ๆญŒๆ›ฒ่‡ณ้šŠๅˆ—ไธญใ€‚", - "playlistOverText": "ๆŠฑๆญ‰๏ผๆ’ญๆ”พๆธ…ๅ–ฎๅ็จฑไธ่ƒฝ่ถ…้Ž 10 ๅ€‹ๅญ—็ฌฆใ€‚", - "playlistSameName": "ๆŠฑๆญ‰๏ผๆญคๅ็จฑไธ่ƒฝ่ˆ‡ไฝ ็š„ๆ–ฐๅ็จฑ็›ธๅŒใ€‚", - "playlistDeleteError": "ไฝ ็„กๆฌŠๅˆช้™ค้ ่จญๆ’ญๆ”พๆธ…ๅ–ฎใ€‚", - "playlistRemove": "ไฝ ๅทฒ็งป้™คๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`]ใ€‚", - "playlistSendErrorPlayer": "ๆŠฑๆญ‰๏ผไฝ ็„กๆณ•ๅ‘่‡ชๅทฑ็™ผ้€้‚€่ซ‹ใ€‚", - "playlistSendErrorBot": "ๆŠฑๆญ‰๏ผไฝ ็„กๆณ•ๅ‘ๆฉŸๅ™จไบบ็™ผ้€้‚€่ซ‹ใ€‚", - "playlistBelongs": "ๆŠฑๆญ‰๏ผๆญคๆ’ญๆ”พๆธ…ๅ–ฎๅฑฌๆ–ผ <@{0}>ใ€‚", - "playlistShare": "ๆŠฑๆญ‰๏ผๆญคๆ’ญๆ”พๆธ…ๅ–ฎๅทฒ่ˆ‡ {0} ๅ…ฑไบซใ€‚", - "playlistSent": "ๆŠฑๆญ‰๏ผไฝ ๅทฒ็™ผ้€้Ž้‚€่ซ‹ใ€‚", - "noPlaylistAcc": "{0} ๆฒ’ๆœ‰ๅปบ็ซ‹ๆ’ญๆ”พๆธ…ๅ–ฎๅธณๆˆถใ€‚", - "overPlaylistCreation": "ไฝ ไธ่ƒฝๅปบ็ซ‹่ถ…้Ž `{0}` ๅ€‹ๆ’ญๆ”พๆธ…ๅ–ฎ๏ผ", - "playlistExists": "ๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`] ๅทฒๅญ˜ๅœจใ€‚", - "playlistNotInvalidUrl": "่ซ‹่ผธๅ…ฅๆœ‰ๆ•ˆ็š„้€ฃ็ตๆˆ–ๅ…ฌ้–‹็š„ Spotify ๆˆ– YouTube ๆ’ญๆ”พๆธ…ๅ–ฎ้€ฃ็ตใ€‚", - "playlistCreated": "ไฝ ๅทฒๅปบ็ซ‹ `{0}` ๆ’ญๆ”พๆธ…ๅ–ฎใ€‚่ผธๅ…ฅ /playlist view ๆชข่ฆ–ๆ›ดๅคš่ณ‡่จŠใ€‚", - "playlistRenamed": "ไฝ ๅทฒๅฐ‡ `{0}` ๆ›ดๅ็‚บ `{1}`ใ€‚", - "playlistLimitTrack": "ไฝ ๅทฒ้”ๅˆฐ้™ๅˆถ๏ผไฝ ๅช่ƒฝๅฐ‡ `{0}` ้ฆ–ๆญŒๆ›ฒๆทปๅŠ ่‡ณไฝ ็š„ๆ’ญๆ”พๆธ…ๅ–ฎไธญใ€‚", - "playlistPlaylistLink": "ไฝ ็„กๆณ•ไฝฟ็”จๆ’ญๆ”พๆธ…ๅ–ฎ้€ฃ็ตใ€‚", - "playlistStream": "ไฝ ็„กๆณ•ๅฐ‡ไธฒๆตๅฝฑ็‰‡ๆทปๅŠ ่‡ณไฝ ็š„ๆ’ญๆ”พๆธ…ๅ–ฎไธญใ€‚", - "playlistPositionNotFound": "ๆ‰พไธๅˆฐๆ’ญๆ”พๆธ…ๅ–ฎ [`{1}`] ไธญไฝ็ฝฎ็‚บ `{0}` ็š„ๆ›ฒ็›ฎ๏ผ", - "playlistRemoved": "๐Ÿ‘‹ ๅทฒๅพž {1} ็š„ๆ’ญๆ”พๆธ…ๅ–ฎ [`{2}`] ไธญๅˆช้™ค **{0}**ใ€‚", - "playlistClear": "ไฝ ๅทฒๆˆๅŠŸๆธ…้™คๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`]ใ€‚", - "playlistView": "ๆ’ญๆ”พๆธ…ๅ–ฎๆชข่ฆ–ๅ™จ", - "playlistViewDesc": "```ๅ็จฑ | ID: {0} | {1}\n็ธฝๆ›ฒ็›ฎๆ•ธ: {2}\nๆ“ๆœ‰่€…: {3}\n้กžๅž‹: {4}\n```", - "playlistViewPermsValue": "๐Ÿ“– ่ฎ€ๅ–: โœ“ โœ๐Ÿฝ ็ทจ่ผฏ: {0} ๐Ÿ—‘๏ธ ๅˆช้™ค: {1}", - "playlistViewPermsValue2": "๐Ÿ“– ่ฎ€ๅ–: {0}", - "playlistViewTrack": "้Ÿณ่ปŒ", - "playlistViewPage": "้ ้ข: {0}/{1} | ็ธฝ้•ทๅบฆ: {2}", - "inboxFull": "ๆŠฑๆญ‰๏ผ{0} ็š„ๆ”ถไปถๅŒฃๅทฒๆปฟใ€‚", - "inboxNoMsg": "ๆ‚จ็š„ๆ”ถไปถๅŒฃไธญๆฒ’ๆœ‰ไปปไฝ•่จŠๆฏใ€‚", - "invitationSent": "ๅทฒ็™ผ้€้‚€่ซ‹็ตฆ {0}ใ€‚", - "notInChannel": "{0}๏ผŒๆ‚จๅฟ…้ ˆๅœจ {1} ไธญไฝฟ็”จ่ชž้ŸณๆŒ‡ไปคใ€‚ๅฆ‚ๆžœๆ‚จๅทฒๅœจ่ชž้Ÿณ้ ป้“ไธญ๏ผŒ่ซ‹้‡ๆ–ฐๅŠ ๅ…ฅ๏ผ", - "noTrackPlaying": "็พๅœจๆฒ’ๆœ‰ๆญŒๆ›ฒๆญฃๅœจๆ’ญๆ”พ", - "noTrackFound": "ๆ‰พไธๅˆฐ็ฌฆๅˆ่ฉฒๆŸฅ่ฉข็š„ๆญŒๆ›ฒ๏ผ่ซ‹ๆไพ›ๆœ‰ๆ•ˆ็š„็ถฒๅ€ใ€‚", - "noLinkSupport": "ๆœ็ดขๅ‘ฝไปคไธๆ”ฏๆด็ถฒๅ€๏ผ", - "voted": "ๆ‚จๅทฒๆŠ•็ฅจ๏ผ", - "missingPosPerm": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ“กๆ‰่ƒฝๆ›ดๆ”นไฝ็ฝฎใ€‚", - "missingModePerm": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ“กๆ‰่ƒฝๅˆ‡ๆ›ๅพช็’ฐๆจกๅผใ€‚", - "missingQueuePerm": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ“กๆ‰่ƒฝๅพž้šŠๅˆ—ไธญ็งป้™ค้Ÿณ่ปŒใ€‚", - "missingAutoPlayPerm": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ“กๆ‰่ƒฝๅ•Ÿ็”จๆˆ–ๅœ็”จ่‡ชๅ‹•ๆ’ญๆ”พๆจกๅผ๏ผ", - "missingFunctionPerm": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ“กๆ‰่ƒฝไฝฟ็”จๆญคๅŠŸ่ƒฝใ€‚", - "timeFormatError": "ๆ™‚้–“ๆ ผๅผไธๆญฃ็ขบใ€‚ไพ‹ๅฆ‚๏ผš2:42 ๆˆ– 12:39:31", - "lyricsNotFound": "ๆ‰พไธๅˆฐๆญŒ่ฉžใ€‚่ผธๅ…ฅ /lyrics <ๆญŒๆ›ฒๅ็จฑ> <ไฝœ่€…> ๆŸฅๆ‰พๆญŒ่ฉžใ€‚", - "missingTrackInfo": "ๆœ‰ไบ›้Ÿณ่ปŒ่ณ‡่จŠ็ผบๅคฑใ€‚", - "noVoiceChannel": "ๆ‰พไธๅˆฐ่ชž้Ÿณ้ ป้“๏ผ", - "playlistAddError": "ๆ‚จ็„กๆฌŠๅฐ‡ไธฒๆต่ฆ–่จŠๆทปๅŠ ๅˆฐๆ’ญๆ”พๆธ…ๅ–ฎไธญ๏ผ", - "playlistAddError2": "ๆทปๅŠ ้Ÿณ่ปŒๅˆฐๆ’ญๆ”พๆธ…ๅ–ฎๆ™‚็™ผ็”Ÿๅ•้กŒ๏ผ", - "playlistLimited": "ๆ‚จๅทฒ้”ๅˆฐไธŠ้™๏ผๆ‚จๅช่ƒฝๅฐ‡ {0} ้ฆ–ๆญŒๆ›ฒๆทปๅŠ ๅˆฐๆ’ญๆ”พๆธ…ๅ–ฎไธญใ€‚", - "playlistRepeated": "ๆ‚จ็š„ๆ’ญๆ”พๆธ…ๅ–ฎไธญๅทฒ็ถ“ๅญ˜ๅœจ็›ธๅŒ็š„้Ÿณ่ปŒ๏ผ", - "playlistAdded": "โค๏ธ ๅทฒๅฐ‡ **{0}** ๆทปๅŠ ๅˆฐ {1} ็š„ๆ’ญๆ”พๆธ…ๅ–ฎไธญ [`{2}`]!", - "playerDropdown": "้ธๆ“‡่ฆ่ทณ่ฝ‰ๅˆฐ็š„้Ÿณ่ปŒ...", - "playerFilter": "้ธๆ“‡่ฆๅฅ—็”จ็š„็ฏฉ้ธๅ™จ...", - "buttonBack": "่ฟ”ๅ›ž", - "buttonPause": "ๆšซๅœ", - "buttonResume": "็นผ็บŒ", - "buttonSkip": "่ทณ้Ž", - "buttonLeave": "้›ข้–‹", - "buttonLoop": "ๅพช็’ฐ", - "buttonVolumeUp": "ๅขžๅŠ ้Ÿณ้‡", - "buttonVolumeDown": "้™ไฝŽ้Ÿณ้‡", - "buttonVolumeMute": "้œ้Ÿณ", - "buttonVolumeUnmute": "ๅ–ๆถˆ้œ้Ÿณ", - "buttonAutoPlay": "่‡ชๅ‹•ๆ’ญๆ”พ", - "buttonShuffle": "้šจๆฉŸๆ’ญๆ”พ", - "buttonForward": "ๅ‰้€ฒ", - "buttonRewind": "ๅพŒ้€€", - "buttonLyrics": "ๆญŒ่ฉž", - "nowplayingDesc": "**็พๅœจๆ’ญๆ”พ:**\n```{0}```", - "nowplayingField": "ๆŽฅไธ‹ไพ†ๆ’ญๆ”พ:", - "nowplayingLink": "ๅœจ {0} ไธŠๆ”ถ่ฝ", - "connect": "ๅทฒ้€ฃๆŽฅ่‡ณ {0}", - "live": "็›ดๆ’ญ", - "playlistLoad": " ๐ŸŽถ ๅทฒๆทปๅŠ ๆ’ญๆ”พๆธ…ๅ–ฎ **{0}**๏ผŒๅ…ฑ `{1}` ้ฆ–ๆญŒๆ›ฒ่‡ณ้šŠๅˆ—ใ€‚", - "trackLoad": "ๅทฒๆทปๅŠ  **[{0}](<{1}>)**๏ผŒ็”ฑ **{2}** (`{3}`) ้–‹ๅง‹ๆ’ญๆ”พใ€‚\n", - "trackLoad_pos": "ๅทฒๅฐ‡ **[{0}](<{1}>)**๏ผŒ็”ฑ **{2}** (`{3}`) ๆทปๅŠ ๅˆฐ้šŠๅˆ—ไธญไฝ็ฝฎ **{4}**\n", - "searchTitle": "ๆœ็ดขๆŸฅ่ฉข: {0}", - "searchDesc": "โžฅ ๅนณๅฐ: {0} **{1}**\nโžฅ ็ตๆžœ: **{2}**\n\n{3}", - "searchWait": "้ธๆ“‡ๆ‚จๆƒณ่ฆๆทปๅŠ ๅˆฐ้šŠๅˆ—ไธญ็š„ๆญŒๆ›ฒใ€‚", - "searchTimeout": "ๆœ็ดข่ถ…ๆ™‚ใ€‚่ซ‹็จๅพŒๅ†่ฉฆใ€‚", - "searchSuccess": "ๅทฒๅฐ‡ๆญŒๆ›ฒๆทปๅŠ ๅˆฐ้šŠๅˆ—ไธญใ€‚", - "queueTitle": "ๅณๅฐ‡ๆ’ญๆ”พ็š„้šŠๅˆ—:", - "historyTitle": "ๆญทๅฒ้šŠๅˆ—:", - "viewTitle": "้Ÿณๆจ‚้šŠๅˆ—", - "viewDesc": "**็พๆญฃๆ’ญๆ”พ๏ผš[้ปžๆ“Šๆˆ‘]({0}) โฎฏ**\n{1}", - "viewFooter": "้ ๆ•ธ๏ผš{0}/{1} | ็ธฝ้•ทๅบฆ๏ผš{2}", - "pauseError": "ๆ’ญๆ”พๅ™จๅทฒ็ถ“ๆšซๅœใ€‚", - "pauseVote": "{0} ๅทฒๆŠ•็ฅจๆšซๅœๆญŒๆ›ฒใ€‚[{1}/{2}]", - "paused": "ๆ’ญๆ”พๅ™จๅทฒ่ขซ `{0}` ๆšซๅœใ€‚", - "resumeError": "ๆ’ญๆ”พๅ™จๆœชๆšซๅœใ€‚", - "resumeVote": "{0} ๅทฒๆŠ•็ฅจๆขๅพฉๆญŒๆ›ฒใ€‚[{1}/{2}]", - "resumed": "ๆ’ญๆ”พๅ™จๅทฒ่ขซ `{0}` ๆขๅพฉใ€‚", - "shuffleError": "ๅœจๆด—็‰Œไน‹ๅ‰ๅฟ…้ ˆๆทปๅŠ ๆ›ดๅคšๆญŒๆ›ฒๅˆฐ้šŠๅˆ—ไธญใ€‚", - "shuffleVote": "{0} ๅทฒๆŠ•็ฅจๆด—็‰Œ้šŠๅˆ—ใ€‚[{1}/{2}]", - "shuffled": "้šŠๅˆ—ๅทฒ่ขซๆด—็‰Œใ€‚", - "skipError": "ๆฒ’ๆœ‰ๆญŒๆ›ฒๅฏไปฅ่ทณ้Žใ€‚", - "skipVote": "{0} ๅทฒๆŠ•็ฅจ่ทณ้ŽๆญŒๆ›ฒใ€‚[{1}/{2}]", - "skipped": "ๆ’ญๆ”พๅ™จๅทฒ่ขซ `{0}` ่ทณ้ŽๆญŒๆ›ฒใ€‚", - "backVote": "{0} ๅทฒๆŠ•็ฅจ่ทณๅˆฐไธŠไธ€้ฆ–ๆญŒๆ›ฒใ€‚[{1}/{2}]", - "backed": "ๆ’ญๆ”พๅ™จๅทฒ่ขซ `{0}` ่ทณๅˆฐไธŠไธ€้ฆ–ๆญŒๆ›ฒใ€‚", - "leaveVote": "{0} ๅทฒๆŠ•็ฅจๅœๆญขๆ’ญๆ”พๅ™จใ€‚[{1}/{2}]", - "left": "ๆ’ญๆ”พๅ™จๅทฒ่ขซ `{0}` ๅœๆญขใ€‚", - "seek": "ๅฐ‡ๆ’ญๆ”พๅ™จ่จญ็ฝฎๅˆฐ **{0}**ใ€‚", - "repeat": "้‡่ค‡ๆจกๅผๅทฒ่จญ็ฝฎ็‚บ `{0}`ใ€‚", - "cleared": "ๆธ…้™คไบ† `{0}` ไธญ็š„ๆ‰€ๆœ‰ๆญŒๆ›ฒใ€‚", - "removed": "ๅทฒๅพž้šŠๅˆ—ไธญๅˆช้™ค `{0}` ้ฆ–ๆญŒๆ›ฒใ€‚", - "forward": "ๅฐ‡ๆ’ญๆ”พๅ™จๅฟซ้€ฒๅˆฐ **{0}**ใ€‚", - "rewind": "ๅฐ‡ๆ’ญๆ”พๅ™จๅ€’ๅ›žๅˆฐ **{0}**ใ€‚", - "replay": "้‡ๆ–ฐๆ’ญๆ”พ็•ถๅ‰ๆญŒๆ›ฒใ€‚", - "swapped": "ๅทฒไบคๆ› `{0}` ๅ’Œ `{1}`ใ€‚", - "moved": "ๅทฒๅฐ‡ `{0}` ็งปๅ‹•ๅˆฐ `{1}`ใ€‚", - "autoplay": "่‡ชๅ‹•ๆ’ญๆ”พๆจกๅผ็พๅœจ็‚บ **{0}**ใ€‚", - "notdj": "ๆ‚จไธๆ˜ฏDJ๏ผŒ็•ถๅ‰DJ็‚บ {0}ใ€‚", - "djToMe": "ๆ‚จ็„กๆณ•ๅฐ‡DJๆฌŠ้™่ฝ‰็งป็ตฆ่‡ชๅทฑๆˆ–ๆฉŸๅ™จไบบใ€‚", - "djNotInChannel": "`{0}` ไธๅœจ่ชž้Ÿณ้ ป้“ไธญใ€‚", - "djswap": "ๆ‚จๅทฒๅฐ‡DJๆฌŠ้™่ฝ‰็งป็ตฆ `{0}`ใ€‚", - "voicelinkQueueFull": "ๆŠฑๆญ‰๏ผŒๆ‚จๅทฒ้”ๅˆฐ้šŠๅˆ—ไธญ `{0}` ้ฆ–ๆญŒๆ›ฒ็š„ๆœ€ๅคงๆ•ธ้‡๏ผ", - "voicelinkOutofList": "่ซ‹ๆไพ›ๆœ‰ๆ•ˆ็š„ๆญŒๆ›ฒ็ดขๅผ•๏ผ", - "voicelinkDuplicateTrack": "ๆŠฑๆญ‰๏ผŒๆญคๆญŒๆ›ฒๅทฒๅœจ้šŠๅˆ—ไธญใ€‚", - "decodeError": "่งฃ็ขผๆ–‡ไปถๆ™‚ๅ‡บ็พๅ•้กŒ๏ผ", - "invalidStartTime": "็„กๆ•ˆ็š„้–‹ๅง‹ๆ™‚้–“! ๆ™‚้–“ๅฟ…้ ˆๅœจ `00:00` ๅ’Œ `{0}` ไน‹้–“ใ€‚", - "invalidEndTime": "็„กๆ•ˆ็š„็ตๆŸๆ™‚้–“! ๆ™‚้–“ๅฟ…้ ˆๅœจ `00:00` ๅ’Œ `{0}` ไน‹้–“ใ€‚", - "invalidTimeOrder": "็ตๆŸๆ™‚้–“ไธ่ƒฝๅฐๆ–ผๆˆ–็ญ‰ๆ–ผ้–‹ๅง‹ๆ™‚้–“ใ€‚", - "setStageAnnounceTemplate": "ๅฎŒๆˆ๏ผๅพž็พๅœจ้–‹ๅง‹๏ผŒๅƒๆ‚จ็พๅœจ็š„่ชž้Ÿณ็‹€ๆ…‹ๅฐ‡ๆ นๆ“šๆ‚จ็š„ๆจกๆฟๅ‘ฝๅใ€‚ๆ‚จๆ‡‰่ฉฒๅœจๅนพ็ง’้˜ๅ…ง็œ‹ๅˆฐๅฎƒๆ›ดๆ–ฐใ€‚", - "createSongRequestChannel": "ไธ€ๅ€‹ๆญŒๆ›ฒ่ซ‹ๆฑ‚้ ป้“ ({0}) ๅทฒๅปบ็ซ‹๏ผๆ‚จๅฏไปฅๅœจ่ฉฒ้ ป้“ไธญ้€้ŽๆญŒๆ›ฒๅ็จฑๆˆ– URL ้–‹ๅง‹่ฆๆฑ‚ไปปไฝ•ๆญŒๆ›ฒ๏ผŒ่€Œ็„ก้œ€ไฝฟ็”จๆฉŸๅ™จไบบๅ‰็ถดใ€‚" -} \ No newline at end of file diff --git a/langs/DE.json b/langs/DE.json index f33ab0c..593b21e 100644 --- a/langs/DE.json +++ b/langs/DE.json @@ -1,172 +1,262 @@ { - "unknownException": "โš ๏ธ Beim Ausfรผhren des Befehls ist etwas schiefgelaufen! Bitte versuche es spรคter erneut oder tritt unserem Discord-Server bei, um weiteren Support zu erhalten.", - "enabled": "aktiviert", - "disabled": "deaktiviert", - "nodeReconnect": "Bitte versuche es erneut, nachdem sich die Node wieder verbunden hat.", - "noChannel": "Kein Sprachkanal zum Verbinden gefunden. Bitte stelle entweder einen zur Verfรผgung oder trete einem bei.", - "alreadyConnected": "Bereits mit einem Sprachkanal verbunden.", - "noPermission": "Es tut mir leid, ich bin nicht berechtigt, dem Sprachkanal beizutreten oder darin zu sprechen.", - "noCreatePermission": "Es tut mir leid, ich habe keine Berechtigung, einen Song Request Channel zu erstellen.", - "noPlaySource": "Ich kann keine abspielbaren Quellen finden!", - "noPlayer": "Auf diesem Server wurden kein Player gefunden.", - "notVote": "Dieser Befehl erfordert Deine Stimme! Gebe `/vote` ein, um weitere Informationen zu erhalten.", - "missingIntents": "Es tut mir leid, dieser Befehl kann nicht ausgefรผhrt werden, da mir der `({0})` Intent fehlt.", - "languageNotFound": "Kein Sprachpaket gefunden. Bitte wรคhle ein vorhandenes Sprachpaket aus.", - "changedLanguage": "Erfolgreich auf das Sprachpaket `{0}` geรคndert.", - "setPrefix": "Erledigt! Mein Prรคfix ist jetzt `{0}` auf deinem Server. Versuche, `{1}ping` auszufรผhren, um es zu testen.", - "setDJ": "Stelle den DJ auf {0}.", - "setQueue": "Stelle den Warteschlangenmodus auf `{0}` ein.", - "247": "Der 24/7-Modus wurde erfolgreich `{0}`.", - "bypassVote": "Das Abstimmungssystem wurde `{0}`", - "setVolume": "Stelle die Lautstรคrke auf `{0}`%.", - "toggleController": "Der Musikcontroller wurde erfolgreich `{0}`", - "toggleDuplicateTrack": "Du hast das hinzufรผgen von doppelten Tracks in der Warteschlange `{0}`", - "toggleControllerMsg": "Nachrichten vom Musik-Controller wurden erfolgreich `{0}`", - "toggleSilentMsg": "Sie haben {0} stille Nachrichten.", - "settingsMenu": "Servereinstellungen | {0}", - "settingsTitle": "โค๏ธ Grundlegende Informationen:", - "settingsValue": "```Prรคfix: {0}`\nSprache: {1}\nMusik-Controller: {2}\nDJ-Rolle: @{3}\nAbstimmungsumgehung: {4}\n24/7 Play: {5}\nStandard Lautstรคrke: {6}%\nSpielzeit: {7}```", - "settingsTitle2": "๐Ÿ”— Warteschlangeninformationen:", - "settingsValue2": "```Warteschlangenmodus: {0}\nMaximale Songs: {1}\nDoppelte Tracks zulassen: {2}```", - "settingsTitle3": "๐ŸŽค Sprachstatusinfo:", - "settingsPermTitle": "โœจ Berechtigungen:", - "settingsPermValue": "```{0} Administrator\n{1} Guild verwalten\n{2} Kanal verwalten\n{3} Manage_Messages```", - "pingTitle1": "Bot-Info:", - "pingTitle2": "Player Info:", - "pingField1": "```Shard-ID: {0}/{1}\nShard-Latenz: {2:.3f}s {3}\nRegion: {4}```", - "pingField2": "```Node: {0} - {1:.3f}s\nPlayer: {2}\nSprachregion: {3}```", - "addEffect": "Wende den Effekt `{0}` Filter an.", - "clearEffect": "Die Soundeffekte wurden gelรถscht!", - "filterTagAlreadyInUse": "Diese Soundeffekte sind bereits im Einsatz! Bitte verwende /cleareffect , um sie zu entfernen.", - "playlistViewTitle": "๐Ÿ“œ Alle Playlists von {0}", - "playlistViewHeaders": "ID:,Zeit:,Name:,Tracks:", - "playlistFooter": "Gebe /playlist play [playlist] ein, um die Playlist in die Warteschlange einzufรผgen.", - "playlistNotFound": "Die Wiedergabeliste [`{0}`] wurde nicht gefunden. Gebe /playlist view ein, um Deine gesamte Wiedergabeliste anzuzeigen.", - "playlistNotAccess": "Es tut mir leid! Du bist nicht berechtigt, auf diese Playlist zuzugreifen!", - "playlistNoTrack": "Es tut uns leid! Es gibt keine Titel in der Wiedergabeliste [`{0}`].", - "playlistNotAllow": "Dieser Befehl ist fรผr verknรผpfte und freigegebene Wiedergabelisten nicht zulรคssig.", - "playlistPlay": "Die Playlist [`{0}`] mit `{1}` Songs wurde zur Warteschlange hinzugefรผgt.", - "playlistOverText": "Verzeihung, der Name der Playlist darf nicht mehr als 10 Zeichen enthalten.", - "playlistSameName": "Verzeihung, dieser Name darf nicht mit Deinem neuen Namen identisch sein.", - "playlistDeleteError": "Du kannst die Standard-Wiedergabeliste nicht lรถschen.", - "playlistRemove": "Du hast die Wiedergabeliste [`{0}`] entfernt.", - "playlistSendErrorPlayer": "Entschuldigung, du kannst keine Einladung an dich selber senden.", - "playlistSendErrorBot": "Entschuldigung, Du kannst keine Einladung an eine App senden.", - "playlistBelongs": "Verzeihung, diese Playlist gehรถrt <@{0}>.", - "playlistShare": "Verzeigung, Diese Playlist wurde bereits mit {0} geteilt.", - "playlistSent": "Verzeihung, Du hast bereits eine Einladung gesendet.", - "noPlaylistAcc": "{0} hat noch kein Playlist-Konto erstellt.", - "overPlaylistCreation": "Du kannst nicht mehr als `{0}` Wiedergabelisten erstellen!", - "playlistExists": "Die Playlist [`{0}`] existiert bereits.", - "playlistNotInvalidUrl": "Bitte gebe einen gรผltigen Link oder รถffentlichen Spotify/YouTube-Playlist-Link ein.", - "playlistCreated": "Du hast die Wiedergabeliste `{0}` erstellt. Gebe /playlist view ein, um weitere Informationen zu erhalten.", - "playlistRenamed": "Du hast `{0}` zu `{1}` umbenannt.", - "playlistLimitTrack": "Du hast die Grenze erreicht! Du kannst deiner Playlist nur noch `{0}` Songs hinzufรผgen.", - "playlistPlaylistLink": "Du kannst keine Playlist-Links verwenden.", - "playlistStream": "Du darfst deiner Playlist keine aktiven Streams hinzufรผgen.", - "playlistPositionNotFound": "Die Position `{0}` kann in Deiner Playlist [`{1}`] nicht gefunden werden.", - "playlistRemoved": "๐Ÿ‘‹ {0} aus {1}s Playlist [{2}] entfernt.", - "playlistClear": "Du hast Deine Wiedergabenliste [`{0}`] erfolgreich gelรถscht.", - "playlistView": "Playlist-Viewer", - "playlistViewDesc": "```Name | ID: {0} | {1}\nTitel insgesamt: {2}\nBesitzer: {3}\nTyp: {4}\n```", - "playlistViewPermsValue": "๐Ÿ“– Lesen: โœ“ โœ๐Ÿฝ Schreiben: {0} ๐Ÿ—‘๏ธ Entfernen: {1}", - "playlistViewPermsValue2": "๐Ÿ“– Lesen: {0}", - "playlistViewTrack": "Tracks", - "playlistViewPage": "Seite: {0}/{1} | Gesamtdauer: {2}", - "inboxFull": "Es tut mir leid! Der Posteingang von {0} ist voll.", - "inboxNoMsg": "Es sind keine Nachrichten in Deinem Posteingang.", - "invitationSent": "Einladung an {0} gesendet.", - "notInChannel": "{0}, Du musst in {1} sein, um Sprachbefehle nutzen zu kรถnnen. Bitte betrete den Sprachkanal erneut bei, wenn Du dich im Voice Channel befindest!", - "noTrackPlaying": "Es werden derzeit keine Songs abgespielt", - "noTrackFound": "Es wurden keine Songs mit dieser Abfrage gefunden! Bitte gib eine gรผltige URL an.", - "noLinkSupport": "Der Suchbefehl unterstรผtzt keine Links!", - "voted": "Du hast abgestimmt!", - "missingPosPerm": "Nur der DJ oder Admins kรถnnen die Position รคndern.", - "missingModePerm": "Nur der DJ oder Admins kรถnnen den Wiederholungsmodus wechseln.", - "missingQueuePerm": "Nur der DJ oder Admins kรถnnen Tracks aus der Warteschlange entfernen.", - "missingAutoPlayPerm": "Nur der DJ oder Admins kรถnnen den Autoplay-Modus aktivieren oder deaktivieren!", - "missingFunctionPerm": "Nur DJ oder Admins kรถnnen diese Funktion verwenden.", - "timeFormatError": "Falsches Zeitformat. Beispiel: 2:42 oder 12:39:31", - "lyricsNotFound": "Es wurden keine Songtexte gefunden. Gebe /lyrics ein, um die Songtexte zu finden.", - "missingTrackInfo": "Einige Track-Informationen fehlen.", - "noVoiceChannel": "Dieser Sprachkanal wurde nicht gefunden!", - "playlistAddError": "Du darfst Deiner Wiedergabenliste keine aktiven Streams hinzufรผgen!", - "playlistAddError2": "Es gab ein Problem beim Hinzufรผgen von Tracks zur Wiedergabenliste!", - "playlistLimited": "Du hast das Limit erreicht! Du kannst nur noch {0} Songs zu Deiner Wiedergabenliste hinzufรผgen.", - "playlistRepeated": "In Deiner Wiedergabenliste gibt es bereits den gleichen Track!", - "playlistAdded": "โค๏ธ **{0}** wurde in der Wiedergabenliste [`{2}`] von {1} hinzugefรผgt.", - "playerDropdown": "Wรคhle einen Song aus, um zu รผberspringen ...", - "playerFilter": "Wรคhle einen Filter aus, um ihn anzuwenden ...", - "buttonBack": "Zurรผck", - "buttonPause": "Pause", - "buttonResume": "Weiter", - "buttonSkip": "รœberspringen", - "buttonLeave": "Verlassen", - "buttonLoop": "Endlosschleife", - "buttonVolumeUp": "Lauter", - "buttonVolumeDown": "Leiser", - "buttonVolumeMute": "Stummschalten", - "buttonVolumeUnmute": "Stummschaltung aufheben", - "buttonAutoPlay": "Autoplay", - "buttonShuffle": "Zufall", - "buttonForward": "Vorspulen", - "buttonRewind": "Zurรผckspulen", - "buttonLyrics": "Liedtexte", - "nowplayingDesc": "**Jetzt wird abgespielt:**\n```{0}```", - "nowplayingField": "Als nรคchstes:", - "nowplayingLink": "Auf {0} anhรถren", - "connect": "Verbunden mit {0}", - "live": "LIVE", - "playlistLoad": " ๐ŸŽถ Die Wiedergabeliste **{0}** mit `{1}` Songs wurde zur Warteschlange hinzugefรผgt.", - "trackLoad": "**[{0}](<{1}>)** von **{2}** (`{3}`) wurde zum Abspielen hinzugefรผgt.\n", - "trackLoad_pos": "**[{0}](<{1}>)** von **{2}** (`{3}`) wurde in der Warteschlange zu Position **{4}** hinzugefรผgt.\n", - "searchTitle": "Suchabfrage: {0}", - "searchDesc": "โžฅ Plattform: {0} **{1}**\nโžฅ Ergebnisse: **{2}**\n\n{3}", - "searchWait": "Wรคhle den Song aus, den Du zur Warteschlange hinzufรผgen mรถchtest.", - "searchTimeout": "Die Suche wurde abgebrochen. Bitte versuche es spรคter erneut.", - "searchSuccess": "Der Song wurde zur Warteschlange hinzugefรผgt.", - "queueTitle": "Kommende Warteschlange:", - "historyTitle": "Vorherige Warteschlange:", - "viewTitle": "Musik-Warteschlange", - "viewDesc": "**Jetzt wird abgespielt: [Hier klicken]({0}) โฎฏ**\n{1}", - "viewFooter": "Seite: {0}/{1} | Gesamtdauer: {2}", - "pauseError": "Der Player ist bereits pausiert.", - "pauseVote": "{0} hat fรผr eine Pause des Songs abgestimmt. [{1}/{2}]", - "paused": "`{0}` hat den Player pausiert.", - "resumeError": "Der Player ist nicht pausiert.", - "resumeVote": "{0} hat fรผr das Fortsetzen des Songs abgestimmt. [{1}/{2}]", - "resumed": "`{0}` hat den Player fortgesetzt.", - "shuffleError": "Fรผge mehr Songs zur Warteschlange hinzu, bevor Du die Songs mischst.", - "shuffleVote": "{0} hat fรผr das Mischen der Warteschlange gestimmt. [{1}/{2}]", - "shuffled": "Die Warteschlange wurde gemischt.", - "skipError": "Es gibt keine Songs, die รผbersprungen werden kรถnnen.", - "skipVote": "{0} hat fรผr das รœberspringen des Songs abgestimmt. [{1}/{2}]", - "skipped": "`{0}` hat den Song รผbersprungen.", - "backVote": "{0} hat fรผr das รœberspringen zum vorherigen Song gestimmt. [{1}/{2}]", - "backed": "{0} hat zum vorherigen Song รผbersprungen.", - "leaveVote": "{0} hat fรผr das Anhalten des Players gestimmt. [{1}/{2}]", - "left": "`{0}` hat den Player angehalten.", - "seek": "Der Player wurde auf **{0}** gesetzt.", - "repeat": "Der Wiederholungsmodus wurde auf `{0}` gesetzt", - "cleared": "Alle Tracks in `{0}` wurden gelรถscht", - "removed": "`{0}` Tracks wurden aus der Warteschlange entfernt.", - "forward": "Der Player wurde auf **{0}** vorgespult.", - "rewind": "Der Player wurde auf **{0}** zurรผckgespult", - "replay": "Der aktuelle Song wird wiederholt.", - "swapped": "`{0}` und `{1}` wurden ausgetauscht.", - "moved": "`{0}` zu `{1}` verschoben", - "autoplay": "Der Autoplay-Modus ist jetzt **{0}**", - "notdj": "Du bist kein DJ, der aktuelle DJ ist {0}.", - "djToMe": "Du kannst den DJ nicht an dich selbst oder einer App รผbertragen.", - "djNotInChannel": "`{0}` ist nicht im Sprachkanal.", - "djswap": "Du hast die DJ-Rolle auf `{0}` รผbertragen.", - "voicelinkQueueFull": "Entschuldigung, Du hast das Maximum von `{0}` Tracks in der Warteschlange erreicht.", - "voicelinkOutofList": "Bitte gib einen gรผltigen Track-Index an!", - "voicelinkDuplicateTrack": "Entschuldigung, dieser Track ist bereits in der Warteschlange.", - "decodeError": "Beim Dekodieren der Datei ist etwas schief gelaufen!", - "invalidStartTime": "Ungรผltige Startzeit! Die Zeit muss innerhalb von `00:00` und `{0}` liegen.", - "invalidEndTime": "Ungรผltige Endzeit! Die Zeit muss innerhalb von `00:00` und `{0}` liegen.", - "invalidTimeOrder": "Die Endzeit darf nicht kleiner oder gleich dem Startzeit sein.", - "setStageAnnounceTemplate": "Fertig! Ab sofort wird der Sprachstatus wie der, in dem Du Dich gerade befindest, gemรครŸ Deiner Vorlage benannt. Du solltest in wenigen Sekunden eine Aktualisierung sehen.", - "createSongRequestChannel": "Der Song Request Channel ({0}) wurde erstellt! Du kannst jeden Song nach Namen oder URL in diesem Kanal anfordern, ohne den Bot-Prรคfix verwenden zu mรผssen." -} \ No newline at end of file + "common": { + "errors": { + "unknown": "โš ๏ธ Beim Ausfรผhren des Befehls ist etwas schiefgelaufen! Bitte versuche es spรคter erneut oder tritt unserem Discord-Server bei, um weiteren Support zu erhalten.", + "missingIntents": "Es tut mir leid, dieser Befehl kann nicht ausgefรผhrt werden, da mir der `({0})` Intent fehlt." + }, + "status": { + "enabled": "aktiviert", + "disabled": "deaktiviert", + "live": "LIVE" + } + }, + "player": { + "errors": { + "nodeReconnect": "Bitte versuche es erneut, nachdem sich die Node wieder verbunden hat.", + "noPlaySource": "Ich kann keine abspielbaren Quellen finden!", + "noPlayer": "Auf diesem Server wurden kein Player gefunden.", + "noTrackPlaying": "Es werden derzeit keine Songs abgespielt", + "noTrackFound": "Es wurden keine Songs mit dieser Abfrage gefunden! Bitte gib eine gรผltige URL an." + }, + "dropdown": { + "trackSelect": "Wรคhle einen Song aus, um zu รผberspringen ...", + "filterSelect": "Wรคhle einen Filter aus, um ihn anzuwenden ..." + }, + "buttons": { + "back": "Zurรผck", + "pause": "Pause", + "resume": "Weiter", + "skip": "รœberspringen", + "leave": "Verlassen", + "loop": "Endlosschleife", + "volumeUp": "Lauter", + "volumeDown": "Leiser", + "volumeMute": "Stummschalten", + "volumeUnmute": "Stummschaltung aufheben", + "autoPlay": "Autoplay", + "shuffle": "Zufall", + "forward": "Vorspulen", + "rewind": "Zurรผckspulen", + "lyrics": "Liedtexte" + }, + "playback": { + "nowplayingDesc": "**Jetzt wird abgespielt:**\n```{0}```", + "nowplayingField": "Als nรคchstes:", + "nowplayingLink": "Auf {0} anhรถren", + "playlistLoad": " ๐ŸŽถ Die Wiedergabeliste **{0}** mit `{1}` Songs wurde zur Warteschlange hinzugefรผgt.", + "trackLoad": "**[{0}](<{1}>)** von **{2}** (`{3}`) wurde zum Abspielen hinzugefรผgt.\n", + "trackLoadPos": "**[{0}](<{1}>)** von **{2}** (`{3}`) wurde in der Warteschlange zu Position **{4}** hinzugefรผgt.\n" + }, + "controls": { + "pause": { + "error": "Der Player ist bereits pausiert.", + "vote": "{0} hat fรผr eine Pause des Songs abgestimmt. [{1}/{2}]", + "success": "`{0}` hat den Player pausiert." + }, + "resume": { + "error": "Der Player ist nicht pausiert.", + "vote": "{0} hat fรผr das Fortsetzen des Songs abgestimmt. [{1}/{2}]", + "success": "`{0}` hat den Player fortgesetzt." + }, + "shuffle": { + "error": "Fรผge mehr Songs zur Warteschlange hinzu, bevor Du die Songs mischst.", + "vote": "{0} hat fรผr das Mischen der Warteschlange gestimmt. [{1}/{2}]", + "success": "Die Warteschlange wurde gemischt." + }, + "skip": { + "error": "Es gibt keine Songs, die รผbersprungen werden kรถnnen.", + "vote": "{0} hat fรผr das รœberspringen des Songs abgestimmt. [{1}/{2}]", + "success": "`{0}` hat den Song รผbersprungen." + }, + "back": { + "vote": "{0} hat fรผr das รœberspringen zum vorherigen Song gestimmt. [{1}/{2}]", + "success": "{0} hat zum vorherigen Song รผbersprungen." + }, + "leave": { + "vote": "{0} hat fรผr das Anhalten des Players gestimmt. [{1}/{2}]", + "success": "`{0}` hat den Player angehalten." + }, + "seek": "Der Player wurde auf **{0}** gesetzt.", + "repeat": "Der Wiederholungsmodus wurde auf `{0}` gesetzt", + "forward": "Der Player wurde auf **{0}** vorgespult.", + "rewind": "Der Player wurde auf **{0}** zurรผckgespult", + "replay": "Der aktuelle Song wird wiederholt.", + "autoplay": "Der Autoplay-Modus ist jetzt **{0}**" + } + }, + "voice": { + "connection": { + "noChannel": "Kein Sprachkanal zum Verbinden gefunden. Bitte stelle entweder einen zur Verfรผgung oder trete einem bei.", + "alreadyConnected": "Bereits mit einem Sprachkanal verbunden.", + "noPermission": "Es tut mir leid, ich bin nicht berechtigt, dem Sprachkanal beizutreten oder darin zu sprechen.", + "notInChannel": "{0}, Du musst in {1} sein, um Sprachbefehle nutzen zu kรถnnen. Bitte betrete den Sprachkanal erneut bei, wenn Du dich im Voice Channel befindest!", + "connect": "Verbunden mit {0}" + }, + "stageChannel": { + "setAnnounceTemplate": "Fertig! Ab sofort wird der Sprachstatus wie der, in dem Du Dich gerade befindest, gemรครŸ Deiner Vorlage benannt. Du solltest in wenigen Sekunden eine Aktualisierung sehen." + } + }, + "permissions": { + "noCreatePermission": "Es tut mir leid, ich habe keine Berechtigung, einen Song Request Channel zu erstellen.", + "missingPosition": "Nur der DJ oder Admins kรถnnen die Position รคndern.", + "missingMode": "Nur der DJ oder Admins kรถnnen den Wiederholungsmodus wechseln.", + "missingQueue": "Nur der DJ oder Admins kรถnnen Tracks aus der Warteschlange entfernen.", + "missingAutoPlay": "Nur der DJ oder Admins kรถnnen den Autoplay-Modus aktivieren oder deaktivieren!", + "missingFunction": "Nur DJ oder Admins kรถnnen diese Funktion verwenden.", + "notDj": "Du bist kein DJ, der aktuelle DJ ist {0}.", + "djToSelf": "Du kannst den DJ nicht an dich selbst oder einer App รผbertragen.", + "djNotInChannel": "`{0}` ist nicht im Sprachkanal.", + "djSwapped": "Du hast die DJ-Rolle auf `{0}` รผbertragen." + }, + "settings": { + "actions": { + "languageNotFound": "Kein Sprachpaket gefunden. Bitte wรคhle ein vorhandenes Sprachpaket aus.", + "languageChanged": "Erfolgreich auf das Sprachpaket `{0}` geรคndert.", + "prefixSet": "Erledigt! Mein Prรคfix ist jetzt `{0}` auf deinem Server. Versuche, `{1}ping` auszufรผhren, um es zu testen.", + "djSet": "Stelle den DJ auf {0}.", + "queueModeSet": "Stelle den Warteschlangenmodus auf `{0}` ein.", + "mode247": "Der 24/7-Modus wurde erfolgreich `{0}`.", + "bypassVote": "Das Abstimmungssystem wurde `{0}`", + "volumeSet": "Stelle die Lautstรคrke auf `{0}`%.", + "controllerToggled": "Der Musikcontroller wurde erfolgreich `{0}`", + "duplicateTrackToggled": "Du hast das hinzufรผgen von doppelten Tracks in der Warteschlange `{0}`", + "controllerMsgToggled": "Nachrichten vom Musik-Controller wurden erfolgreich `{0}`", + "silentMsgToggled": "Sie haben {0} stille Nachrichten." + }, + "menu": "Servereinstellungen | {0}", + "basic": { + "title": "โค๏ธ Grundlegende Informationen:", + "value": "```Prรคfix: {0}`\nSprache: {1}\nMusik-Controller: {2}\nDJ-Rolle: @{3}\nAbstimmungsumgehung: {4}\n24/7 Play: {5}\nStandard Lautstรคrke: {6}%\nSpielzeit: {7}```" + }, + "queue": { + "title": "๐Ÿ”— Warteschlangeninformationen:", + "value": "```Warteschlangenmodus: {0}\nMaximale Songs: {1}\nDoppelte Tracks zulassen: {2}```" + }, + "voice": { + "title": "๐ŸŽค Sprachstatusinfo:" + }, + "permissions": { + "title": "โœจ Berechtigungen:", + "value": "```{0} Administrator\n{1} Guild verwalten\n{2} Kanal verwalten\n{3} Manage_Messages```" + } + }, + "ping": { + "title1": "Bot-Info:", + "title2": "Player Info:", + "field1": "```Shard-ID: {0}/{1}\nShard-Latenz: {2:.3f}s {3}\nRegion: {4}```", + "field2": "```Node: {0} - {1:.3f}s\nPlayer: {2}\nSprachregion: {3}```" + }, + "effects": { + "added": "Wende den Effekt `{0}` Filter an.", + "cleared": "Die Soundeffekte wurden gelรถscht!", + "tagInUse": "Diese Soundeffekte sind bereits im Einsatz! Bitte verwende /cleareffect , um sie zu entfernen." + }, + "playlist": { + "view": { + "title": "Alle Playlists von {0}", + "headers": "ID:,Zeit:,Name:,Tracks:", + "footer": "Gebe /playlist play [playlist] ein, um die Playlist in die Warteschlange einzufรผgen.", + "detailTitle": "Playlist-Viewer", + "detailDesc": "```Name: {0} [{1}]\nTitel insgesamt: {2}\nBesitzer: {3}\nTyp: {4}\n```", + "permsValue": "๐Ÿ“– Lesen: โœ“ โœ๐Ÿฝ Schreiben: {0} ๐Ÿ—‘๏ธ Entfernen: {1}", + "permsValue2": "๐Ÿ“– Lesen: {0}", + "trackList": "Tracks", + "footer2": "Gesamtdauer: {0}", + "playlistSelect": "Wรคhlen Sie eine Wiedergabeliste zur Ansicht ..." + }, + "errors": { + "notFound": "Die Wiedergabeliste [`{0}`] wurde nicht gefunden. Gebe /playlist view ein, um Deine gesamte Wiedergabeliste anzuzeigen.", + "noAccess": "Es tut mir leid! Du bist nicht berechtigt, auf diese Playlist zuzugreifen!", + "noTrack": "Es tut uns leid! Es gibt keine Titel in der Wiedergabeliste [`{0}`].", + "notAllowed": "Dieser Befehl ist fรผr verknรผpfte und freigegebene Wiedergabelisten nicht zulรคssig.", + "nameOverLimit": "Verzeihung, der Name der Playlist darf nicht mehr als 10 Zeichen enthalten.", + "sameName": "Verzeihung, dieser Name darf nicht mit Deinem neuen Namen identisch sein.", + "deleteDefault": "Du kannst die Standard-Wiedergabeliste nicht lรถschen.", + "limitReached": "Du kannst nicht mehr als `{0}` Wiedergabelisten erstellen!", + "exists": "Die Playlist [`{0}`] existiert bereits.", + "invalidUrl": "Bitte gebe einen gรผltigen Link oder รถffentlichen Spotify/YouTube-Playlist-Link ein.", + "trackLimitReached": "Du hast die Grenze erreicht! Du kannst deiner Playlist nur noch `{0}` Songs hinzufรผgen.", + "playlistLinkNotAllowed": "Du kannst keine Playlist-Links verwenden.", + "streamNotAllowed": "Du darfst deiner Playlist keine aktiven Streams hinzufรผgen.", + "positionNotFound": "Die Position `{0}` kann in Deiner Playlist [`{1}`] nicht gefunden werden.", + "streaming": "Du darfst Deiner Wiedergabenliste keine aktiven Streams hinzufรผgen!", + "track": "Es gab ein Problem beim Hinzufรผgen von Tracks zur Wiedergabenliste!", + "trackLimited": "Du hast das Limit erreicht! Du kannst nur noch {0} Songs zu Deiner Wiedergabenliste hinzufรผgen.", + "trackRepeated": "In Deiner Wiedergabenliste gibt es bereits den gleichen Track!" + }, + "actions": { + "play": "Die Playlist [`{0}`] mit `{1}` Songs wurde zur Warteschlange hinzugefรผgt.", + "removed": "Du hast die Wiedergabeliste [`{0}`] entfernt.", + "created": "Du hast die Wiedergabeliste `{0}` erstellt. Gebe /playlist view ein, um weitere Informationen zu erhalten.", + "renamed": "Du hast `{0}` zu `{1}` umbenannt.", + "trackRemoved": "๐Ÿ‘‹ {0} aus {1}s Playlist [{2}] entfernt.", + "cleared": "Du hast Deine Wiedergabenliste [`{0}`] erfolgreich gelรถscht.", + "trackAdded": "โค๏ธ **{0}** wurde in der Wiedergabenliste [`{2}`] von {1} hinzugefรผgt." + }, + "sharing": { + "sendErrorPlayer": "Entschuldigung, du kannst keine Einladung an dich selber senden.", + "sendErrorBot": "Entschuldigung, Du kannst keine Einladung an eine App senden.", + "belongs": "Verzeihung, diese Playlist gehรถrt <@{0}>.", + "alreadyShared": "Verzeigung, Diese Playlist wurde bereits mit {0} geteilt.", + "alreadySent": "Verzeihung, Du hast bereits eine Einladung gesendet.", + "noAccount": "{0} hat noch kein Playlist-Konto erstellt.", + "invitationSent": "Einladung an {0} gesendet." + }, + "inbox": { + "full": "Es tut mir leid! Der Posteingang von {0} ist voll.", + "noMessages": "Es sind keine Nachrichten in Deinem Posteingang." + } + }, + "search": { + "noLinkSupport": "Der Suchbefehl unterstรผtzt keine Links!", + "title": "Suchabfrage: {0}", + "desc": "โžฅ Plattform: {0} **{1}**\nโžฅ Ergebnisse: **{2}**\n\n{3}", + "wait": "Wรคhle den Song aus, den Du zur Warteschlange hinzufรผgen mรถchtest.", + "success": "Der Song wurde zur Warteschlange hinzugefรผgt." + }, + "voting": { + "voted": "Du hast abgestimmt!" + }, + "time": { + "formatError": "Falsches Zeitformat. Beispiel: 2:42 oder 12:39:31", + "invalidStartTime": "Ungรผltige Startzeit! Die Zeit muss innerhalb von `00:00` und `{0}` liegen.", + "invalidEndTime": "Ungรผltige Endzeit! Die Zeit muss innerhalb von `00:00` und `{0}` liegen." + }, + "lyrics": { + "notFound": "Es wurden keine Songtexte gefunden. Gebe /lyrics ein, um die Songtexte zu finden." + }, + "queue": { + "management": { + "title": "Kommende Warteschlange:", + "cleared": "Alle Tracks in `{0}` wurden gelรถscht", + "removed": "`{0}` Tracks wurden aus der Warteschlange entfernt.", + "swapped": "`{0}` und `{1}` wurden ausgetauscht.", + "moved": "`{0}` zu `{1}` verschoben" + }, + "view": { + "historyTitle": "Vorherige Warteschlange:", + "title": "Musik-Warteschlange", + "desc": "**Jetzt wird abgespielt: [Hier klicken]({0}) โฎฏ**\n{1}" + }, + "errors": { + "queueFull": "Entschuldigung, Du hast das Maximum von `{0}` Tracks in der Warteschlange erreicht.", + "outOfList": "Bitte gib einen gรผltigen Track-Index an!", + "duplicateTrack": "Entschuldigung, dieser Track ist bereits in der Warteschlange." + } + }, + "songRequest": { + "channelCreated": "Der Song Request Channel ({0}) wurde erstellt! Du kannst jeden Song nach Namen oder URL in diesem Kanal anfordern, ohne den Bot-Prรคfix verwenden zu mรผssen." + }, + "pagination": { + "prev": "Zurรผck", + "next": "Weiter", + "play": "Spielen", + "share": "Teilen", + "export": "Exportieren", + "delete": "Lรถschen", + "page": { + "title": "Seitenzahl eingeben", + "field": "Seitenzahl" + } + } +} diff --git a/langs/EN.json b/langs/EN.json index 378a207..28dc5a9 100644 --- a/langs/EN.json +++ b/langs/EN.json @@ -1,172 +1,262 @@ { - "unknownException": "โš ๏ธ Something wrong while running the command! Please try again later or join our discord server for further support.", - "enabled": "enabled", - "disabled": "disabled", - "nodeReconnect": "Please try again! After the node reconnects.", - "noChannel": "No voice channel to connect. Please either provide one or join one.", - "alreadyConnected": "Already connected to a voice channel.", - "noPermission": "Sorry! i don't have permissions to join or speak in your voice channel.", - "noCreatePermission": "Sorry! i don't have permissions to create a song requesting channel.", - "noPlaySource": "Can't find any playable sources!", - "noPlayer": "No player has been found on this server.", - "notVote": "This command requires your vote! Type `/vote` for more info.", - "missingIntents": "Sorry, this command cannot be executed because the bot is missing the required request intent: `({0})`.", - "languageNotFound": "No language pack found! please select an existing language pack.", - "changedLanguage": "Successfully changed to `{0}` language pack.", - "setPrefix": "Done! My prefix in your server is now `{0}`. Try running `{1}ping` to test it out.", - "setDJ": "Set the dj to {0}.", - "setQueue": "Set queue mode to `{0}`.", - "247": "24/7 mode is now `{0}`.", - "bypassVote": "You have `{0}` voting system.", - "setVolume": "Set the volume to `{0}`%", - "toggleController": "You now have `{0}` the music controller.", - "toggleDuplicateTrack": "You have now `{0}` duplicate track prevention.", - "toggleControllerMsg": "You now have `{0}` messages from the music controller.", - "toggleSilentMsg": "You have {0} silent messaging.", - "settingsMenu": "Server Settings | {0}", - "settingsTitle": "โค๏ธ Basic Info:", - "settingsValue": "```Prefix: {0}\nLanguage: {1}\nMusic Controller: {2}\nDJ Role: @{3}\nVote Bypass: {4}\n24/7: {5}\nDefault Volume: {6}%\nPlay Time: {7}```", - "settingsTitle2": "๐Ÿ”— Queue info:", - "settingsValue2": "```Queue Mode: {0}\nMax Song: {1}\nDuplicate Track: {2}```", - "settingsTitle3": "๐ŸŽค Voice Status Info:", - "settingsPermTitle": "โœจ Permissions:", - "settingsPermValue": "```{0} Administrator\n{1} Manage_Guild\n{2} Manage_Channel\n{3} Manage_Messages```", - "pingTitle1": "Bot Info:", - "pingTitle2": "Player Info:", - "pingField1": "```Shard ID: {0}/{1}\nShard Latency: {2:.3f}s {3}\nRegion: {4}```", - "pingField2": "```Node: {0} - {1:.3f}s\nPlayers: {2}\nVoice Region: {3}```", - "addEffect": "Applied the `{0}` effect.", - "clearEffect": "The sound effects have been cleared!", - "filterTagAlreadyInUse": "This sound effect is already in use! Please use /cleareffect to remove it.", - "playlistViewTitle": "๐Ÿ“œ All of {0}'s Playlists", - "playlistViewHeaders": "ID:,Time:,Name:,Tracks:", - "playlistFooter": "Type /playlist play [playlist] to add a playlist the into the queue.", - "playlistNotFound": "Playlist [`{0}`] not found. Type /playlist view to view all your playlist.", - "playlistNotAccess": "Sorry! You are not allowed to access this playlist!", - "playlistNoTrack": "Sorry! There are no tracks in the playlist [`{0}`].", - "playlistNotAllow": "This command is not allowed on linked and shared playlists.", - "playlistPlay": "Added the playlist [`{0}`] with `{1}` songs to the queue.", - "playlistOverText": "Sorry! The name of the playlist cannot exceed 10 characters.", - "playlistSameName": "Sorry! Please select a new name for the playlist.", - "playlistDeleteError": "You are not allowed to delete the default playlist.", - "playlistRemove": "You have removed playlist [`{0}`].", - "playlistSendErrorPlayer": "Sorry! You are not able to send an invitation to yourself.", - "playlistSendErrorBot": "Sorry! You are not able to send an invitation to a bot.", - "playlistBelongs": "Sorry! This playlist belongs to <@{0}>.", - "playlistShare": "Sorry! This playlist has been shared with {0}.", - "playlistSent": "Sorry! You have already sent an invitation to that user before.", - "noPlaylistAcc": "{0} didn't create a playlist account.", - "overPlaylistCreation": "You cannot create more than `{0}` playlists!", - "playlistExists": "Playlist [`{0}`] already exists.", - "playlistNotInvalidUrl": "Please enter a valid public spotify or youtube playlist link.", - "playlistCreated": "You have created `{0}` playlist. Type /playlist view for more info.", - "playlistRenamed": "You have renamed `{0}` to `{1}`.", - "playlistLimitTrack": "You have reached the limit! You can only add `{0}` songs to your playlist.", - "playlistPlaylistLink": "You are not allowed to use playlist links.", - "playlistStream": "You are not allowed to add streaming videos to your playlist.", - "playlistPositionNotFound": "Can't find position `{0}` from your playlist [`{1}`]!", - "playlistRemoved": "๐Ÿ‘‹ Removed **{0}** from {1}'s playlist [`{2}`].", - "playlistClear": "You have successfully clear your playlist [`{0}`].", - "playlistView": "Playlist Viewer", - "playlistViewDesc": "```Name | ID: {0} | {1}\nTotal Tracks: {2}\nOwner: {3}\nType: {4}\n```", - "playlistViewPermsValue": "๐Ÿ“– Read: โœ“ โœ๐Ÿฝ Write: {0} ๐Ÿ—‘๏ธ Remove: {1}", - "playlistViewPermsValue2": "๐Ÿ“– Read: {0}", - "playlistViewTrack": "Tracks", - "playlistViewPage": "Page: {0}/{1} | Total Duration: {2}", - "inboxFull": "Sorry! {0}'s inbox is full.", - "inboxNoMsg": "There are no messages in your inbox.", - "invitationSent": "Invitation sent to {0}.", - "notInChannel": "{0}, you must be in {1} to use voice commands. Please rejoin if you are in voice!", - "noTrackPlaying": "There are no songs playing right now", - "noTrackFound": "No songs were found with that query! Please provide a valid url.", - "noLinkSupport": "Search command does not support links!", - "voted": "You have voted!", - "missingPosPerm": "Only the DJ or admins may change the position.", - "missingModePerm": "Only the DJ or admins may switch loop mode.", - "missingQueuePerm": "Only the DJ or admins may remove track from the queue.", - "missingAutoPlayPerm": "Only the DJ or admins can enable or disable autoplay mode!", - "missingFunctionPerm": "Only DJ or Admin can use this function.", - "timeFormatError": "Incorrect time format. Example: 2:42 or 12:39:31", - "lyricsNotFound": "Lyrics not found. Type /lyrics to find the lyrics.", - "missingTrackInfo": "Some track details are missing.", - "noVoiceChannel": "Voice Channel Not Found!", - "playlistAddError": "You are not allowed to add streaming videos to your playlist!", - "playlistAddError2": "There was a problem adding tracks to the playlist!", - "playlistLimited": "You have reached the limit! You can only add {0} songs to your playlist.", - "playlistRepeated": "This track is already in your playlist!", - "playlistAdded": "โค๏ธ Added **{0}** into {1}'s playlist [`{2}`]!", - "playerDropdown": "Select a song to skip to ...", - "playerFilter": "Select a filter to apply ...", - "buttonBack": "Back", - "buttonPause": "Pause", - "buttonResume": "Resume", - "buttonSkip": "Skip", - "buttonLeave": "Leave", - "buttonLoop": "Loop", - "buttonVolumeUp": "Volume Up", - "buttonVolumeDown": "Volume Down", - "buttonVolumeMute": "Mute", - "buttonVolumeUnmute": "Unmute", - "buttonAutoPlay": "Autoplay", - "buttonShuffle": "Shuffle", - "buttonForward": "Forward", - "buttonRewind": "Rewind", - "buttonLyrics": "Lyrics", - "nowplayingDesc": "**Now Playing:**\n```{0}```", - "nowplayingField": "Up Next:", - "nowplayingLink": "Listen on {0}", - "connect": "Connected to {0}", - "live": "LIVE", - "playlistLoad": " ๐ŸŽถ Added the playlist **{0}** with `{1}` songs to the queue.", - "trackLoad": "Added **[{0}](<{1}>)** by **{2}** (`{3}`) to begin playing.\n", - "trackLoad_pos": "Added **[{0}](<{1}>)** by **{2}** (`{3}`) to the queue at position **{4}**\n", - "searchTitle": "Search Query: {0}", - "searchDesc": "โžฅ Platform: {0} **{1}**\nโžฅ Results: **{2}**\n\n{3}", - "searchWait": "Select the song you want to add to the queue.", - "searchTimeout": "Search timed out. Please try again later.", - "searchSuccess": "Song added to the queue.", - "queueTitle": "Upcoming Queue:", - "historyTitle": "History Queue:", - "viewTitle": "Music Queue", - "viewDesc": "**Now Playing: [Click Me]({0}) โฎฏ**\n{1}", - "viewFooter": "Page: {0}/{1} | Total Duration: {2}", - "pauseError": "The player is already paused.", - "pauseVote": "{0} has voted to pause the song. [{1}/{2}]", - "paused": "`{0}` has paused the player.", - "resumeError": "The player is not paused.", - "resumeVote": "{0} has voted to resume the song. [{1}/{2}]", - "resumed": "`{0}` has resumed the player.", - "shuffleError": "Add more songs to the queue before shuffling.", - "shuffleVote": "{0} has voted to shuffle the queue. [{1}/{2}]", - "shuffled": "The queue is shuffled.", - "skipError": "There are no songs too skip to.", - "skipVote": "{0} has voted to skip the song. [{1}/{2}]", - "skipped": "`{0}` has skipped the song.", - "backVote": "{0} has voted to skip to the previous song. [{1}/{2}]", - "backed": "`{0}` has skipping to previous song.", - "leaveVote": "{0} has voted to stop the player. [{1}/{2}]", - "left": "`{0}` has stopped the player.", - "seek": "Set the player to **{0}**", - "repeat": "The repeat mode has been set to `{0}`", - "cleared": "Cleared all tracks in the `{0}`", - "removed": "`{0}` tracks were removed from the queue.", - "forward": "Forward the player to **{0}**", - "rewind": "Rewound the player to **{0}**", - "replay": "Replaying the current song.", - "swapped": "`{0}` and `{1}` have been swapped", - "moved": "Moved `{0}` to `{1}`", - "autoplay": "Autoplay mode is now **{0}**", - "notdj": "You are not DJ. The current DJ is {0}.", - "djToMe": "You cannot transfer role of DJ to yourself or a bot.", - "djNotInChannel": "`{0}` is not in the voice channel.", - "djswap": "You have transferred the role of dj to `{0}`.", - "voicelinkQueueFull": "Sorry, you have reached the maximum of `{0}` tracks in the queue!", - "voicelinkOutofList": "Please provide a valid track index!", - "voicelinkDuplicateTrack": "Sorry, this track is already in the queue.", - "decodeError": "Something went wrong while decoding the file!", - "invalidStartTime": "Invalid start time, it must be between `00:00` and `{0}`", - "invalidEndTime": "Invalid end time, it must be between `00:00` and `{0}`", - "invalidTimeOrder": "End time cannot be less than or equal to start time", - "setStageAnnounceTemplate": "Done! From now on, voice status like the one you're in now will be named according to your template. You should see it update in a few seconds.", - "createSongRequestChannel": "A song request channel ({0}) has been created! You can start requesting any song by name or URL in that channel, without needing to use the bot prefix." -} \ No newline at end of file + "common": { + "errors": { + "unknown": "โš ๏ธ Something wrong while running the command! Please try again later or join our discord server for further support.", + "missingIntents": "Sorry, this command cannot be executed because the bot is missing the required request intent: `({0})`." + }, + "status": { + "enabled": "enabled", + "disabled": "disabled", + "live": "LIVE" + } + }, + "player": { + "errors": { + "nodeReconnect": "Please try again! After the node reconnects.", + "noPlaySource": "Can't find any playable sources!", + "noPlayer": "No player has been found on this server.", + "noTrackPlaying": "There are no songs playing right now", + "noTrackFound": "No songs were found with that query! Please provide a valid url." + }, + "dropdown": { + "trackSelect": "Select a song to skip to ...", + "filterSelect": "Select a filter to apply ..." + }, + "buttons": { + "back": "Back", + "pause": "Pause", + "resume": "Resume", + "skip": "Skip", + "leave": "Leave", + "loop": "Loop", + "volumeUp": "Volume Up", + "volumeDown": "Volume Down", + "volumeMute": "Mute", + "volumeUnmute": "Unmute", + "autoPlay": "Autoplay", + "shuffle": "Shuffle", + "forward": "Forward", + "rewind": "Rewind", + "lyrics": "Lyrics" + }, + "playback": { + "nowplayingDesc": "**Now Playing:**\n```{0}```", + "nowplayingField": "Up Next:", + "nowplayingLink": "Listen on {0}", + "playlistLoad": " ๐ŸŽถ Added the playlist **{0}** with `{1}` songs to the queue.", + "trackLoad": "Added **[{0}](<{1}>)** by **{2}** (`{3}`) to begin playing.\n", + "trackLoadPos": "Added **[{0}](<{1}>)** by **{2}** (`{3}`) to the queue at position **{4}**\n" + }, + "controls": { + "pause": { + "error": "The player is already paused.", + "vote": "{0} has voted to pause the song. [{1}/{2}]", + "success": "`{0}` has paused the player." + }, + "resume": { + "error": "The player is not paused.", + "vote": "{0} has voted to resume the song. [{1}/{2}]", + "success": "`{0}` has resumed the player." + }, + "shuffle": { + "error": "Add more songs to the queue before shuffling.", + "vote": "{0} has voted to shuffle the queue. [{1}/{2}]", + "success": "The queue is shuffled." + }, + "skip": { + "error": "There are no songs too skip to.", + "vote": "{0} has voted to skip the song. [{1}/{2}]", + "success": "`{0}` has skipped the song." + }, + "back": { + "vote": "{0} has voted to skip to the previous song. [{1}/{2}]", + "success": "`{0}` has skipping to previous song." + }, + "leave": { + "vote": "{0} has voted to stop the player. [{1}/{2}]", + "success": "`{0}` has stopped the player." + }, + "seek": "Set the player to **{0}**", + "repeat": "The repeat mode has been set to `{0}`", + "forward": "Forward the player to **{0}**", + "rewind": "Rewound the player to **{0}**", + "replay": "Replaying the current song.", + "autoplay": "Autoplay mode is now **{0}**" + } + }, + "voice": { + "connection": { + "noChannel": "No voice channel to connect. Please either provide one or join one.", + "alreadyConnected": "Already connected to a voice channel.", + "noPermission": "Sorry! i don't have permissions to join or speak in your voice channel.", + "notInChannel": "{0}, you must be in {1} to use voice commands. Please rejoin if you are in voice!", + "connect": "Connected to {0}" + }, + "stageChannel": { + "setAnnounceTemplate": "Done! From now on, voice status like the one you're in now will be named according to your template. You should see it update in a few seconds." + } + }, + "permissions": { + "noCreatePermission": "Sorry! i don't have permissions to create a song requesting channel.", + "missingPosition": "Only the DJ or admins may change the position.", + "missingMode": "Only the DJ or admins may switch loop mode.", + "missingQueue": "Only the DJ or admins may remove track from the queue.", + "missingAutoPlay": "Only the DJ or admins can enable or disable autoplay mode!", + "missingFunction": "Only DJ or Admin can use this function.", + "notDj": "You are not DJ. The current DJ is {0}.", + "djToSelf": "You cannot transfer role of DJ to yourself or a bot.", + "djNotInChannel": "`{0}` is not in the voice channel.", + "djSwapped": "You have transferred the role of dj to `{0}`." + }, + "settings": { + "actions": { + "languageNotFound": "No language pack found! please select an existing language pack.", + "languageChanged": "Successfully changed to `{0}` language pack.", + "prefixSet": "Done! My prefix in your server is now `{0}`. Try running `{1}ping` to test it out.", + "djSet": "Set the dj to {0}.", + "queueModeSet": "Set queue mode to `{0}`.", + "mode247": "24/7 mode is now `{0}`.", + "bypassVote": "You have `{0}` voting system.", + "volumeSet": "Set the volume to `{0}`%", + "controllerToggled": "You now have `{0}` the music controller.", + "duplicateTrackToggled": "You have now `{0}` duplicate track prevention.", + "controllerMsgToggled": "You now have `{0}` messages from the music controller.", + "silentMsgToggled": "You have {0} silent messaging." + }, + "menu": "Server Settings | {0}", + "basic": { + "title": "โค๏ธ Basic Info:", + "value": "```Prefix: {0}\nLanguage: {1}\nMusic Controller: {2}\nDJ Role: @{3}\nVote Bypass: {4}\n24/7: {5}\nDefault Volume: {6}%\nPlay Time: {7}```" + }, + "queue": { + "title": "๐Ÿ”— Queue info:", + "value": "```Queue Mode: {0}\nMax Song: {1}\nDuplicate Track: {2}```" + }, + "voice": { + "title": "๐ŸŽค Voice Status Info:" + }, + "permissions": { + "title": "โœจ Permissions:", + "value": "```{0} Administrator\n{1} Manage_Guild\n{2} Manage_Channel\n{3} Manage_Messages```" + } + }, + "ping": { + "title1": "Bot Info:", + "title2": "Player Info:", + "field1": "```Shard ID: {0}/{1}\nShard Latency: {2:.3f}s {3}\nRegion: {4}```", + "field2": "```Node: {0} - {1:.3f}s\nPlayers: {2}\nVoice Region: {3}```" + }, + "effects": { + "added": "Applied the `{0}` effect.", + "cleared": "The sound effects have been cleared!", + "tagInUse": "This sound effect is already in use! Please use /cleareffect to remove it." + }, + "playlist": { + "view": { + "title": "All of {0}'s Playlists", + "headers": "ID:,Time:,Name:,Tracks:", + "footer": "Type /playlist play [playlist] to add a playlist the into the queue.", + "detailTitle": "Playlist Viewer", + "detailDesc": "```Name: {0} [{1}]\nTotal Tracks: {2}\nOwner: {3}\nType: {4}\n```", + "permsValue": "๐Ÿ“– Read: โœ“ โœ๐Ÿฝ Write: {0} ๐Ÿ—‘๏ธ Remove: {1}", + "permsValue2": "๐Ÿ“– Read: {0}", + "trackList": "Tracks", + "footer2": "Total Duration: {0}", + "playlistSelect": "Select a playlist to view ..." + }, + "errors": { + "notFound": "Playlist [`{0}`] not found. Type /playlist view to view all your playlist.", + "noAccess": "Sorry! You are not allowed to access this playlist!", + "noTrack": "Sorry! There are no tracks in the playlist [`{0}`].", + "notAllowed": "This command is not allowed on linked and shared playlists.", + "nameOverLimit": "Sorry! The name of the playlist cannot exceed 10 characters.", + "sameName": "Sorry! Please select a new name for the playlist.", + "deleteDefault": "You are not allowed to delete the default playlist.", + "limitReached": "You cannot create more than `{0}` playlists!", + "exists": "Playlist [`{0}`] already exists.", + "invalidUrl": "Please enter a valid public spotify or youtube playlist link.", + "trackLimitReached": "You have reached the limit! You can only add `{0}` songs to your playlist.", + "playlistLinkNotAllowed": "You are not allowed to use playlist links.", + "streamNotAllowed": "You are not allowed to add streaming videos to your playlist.", + "positionNotFound": "Can't find position `{0}` from your playlist [`{1}`]!", + "streaming": "You are not allowed to add streaming videos to your playlist!", + "track": "There was a problem adding tracks to the playlist!", + "trackLimited": "You have reached the limit! You can only add {0} songs to your playlist.", + "trackRepeated": "This track is already in your playlist!" + }, + "actions": { + "play": "Added the playlist [`{0}`] with `{1}` songs to the queue.", + "removed": "You have removed playlist [`{0}`].", + "created": "You have created `{0}` playlist. Type /playlist view for more info.", + "renamed": "You have renamed `{0}` to `{1}`.", + "trackRemoved": "๐Ÿ‘‹ Removed **{0}** from {1}'s playlist [`{2}`].", + "cleared": "You have successfully clear your playlist [`{0}`].", + "trackAdded": "โค๏ธ Added **{0}** into {1}'s playlist [`{2}`]!" + }, + "sharing": { + "sendErrorPlayer": "Sorry! You are not able to send an invitation to yourself.", + "sendErrorBot": "Sorry! You are not able to send an invitation to a bot.", + "belongs": "Sorry! This playlist belongs to <@{0}>.", + "alreadyShared": "Sorry! This playlist has been shared with {0}.", + "alreadySent": "Sorry! You have already sent an invitation to that user before.", + "noAccount": "{0} didn't create a playlist account.", + "invitationSent": "Invitation sent to {0}." + }, + "inbox": { + "full": "Sorry! {0}'s inbox is full.", + "noMessages": "There are no messages in your inbox." + } + }, + "search": { + "noLinkSupport": "Search command does not support links!", + "title": "Search Query: {0}", + "desc": "โžฅ Platform: {0} **{1}**\nโžฅ Results: **{2}**\n\n{3}", + "wait": "Select the song you want to add to the queue.", + "success": "Song added to the queue." + }, + "voting": { + "voted": "You have voted!" + }, + "time": { + "formatError": "Incorrect time format. Example: 2:42 or 12:39:31", + "invalidStartTime": "Invalid start time, it must be between `00:00` and `{0}`", + "invalidEndTime": "Invalid end time, it must be between `00:00` and `{0}`" + }, + "lyrics": { + "notFound": "Lyrics not found. Type /lyrics to find the lyrics." + }, + "queue": { + "management": { + "title": "Upcoming Queue:", + "cleared": "Cleared all tracks in the `{0}`", + "removed": "`{0}` tracks were removed from the queue.", + "swapped": "`{0}` and `{1}` have been swapped", + "moved": "Moved `{0}` to `{1}`" + }, + "view": { + "historyTitle": "History Queue:", + "title": "Music Queue", + "desc": "**Now Playing: [Click Me]({0}) โฎฏ**\n{1}" + }, + "errors": { + "queueFull": "Sorry, you have reached the maximum of `{0}` tracks in the queue!", + "outOfList": "Please provide a valid track index!", + "duplicateTrack": "Sorry, this track is already in the queue." + } + }, + "songRequest": { + "channelCreated": "A song request channel ({0}) has been created! You can start requesting any song by name or URL in that channel, without needing to use the bot prefix." + }, + "pagination": { + "prev": "Prev", + "next": "Next", + "play": "Play", + "share": "Share", + "export": "Export", + "delete": "Delete", + "page": { + "title": "Enter Page Number", + "field": "Page Number" + } + } +} diff --git a/langs/ES.json b/langs/ES.json index b2ec7bb..24460b2 100644 --- a/langs/ES.json +++ b/langs/ES.json @@ -1,172 +1,262 @@ { - "unknownException": "โš ๏ธ ยกAlgo saliรณ mal al ejecutar el comando! Por favor, intente de nuevo mรกs tarde o รบnase a nuestro servidor de Discord para obtener mรกs soporte.", - "enabled": "habilitado", - "disabled": "deshabilitado", - "nodeReconnect": "ยกPor favor, intรฉntelo de nuevo! Despuรฉs de que el nodo se reconecte.", - "noChannel": "No hay canal de voz al que conectarse. Por favor, proporcione uno o รบnase a uno.", - "alreadyConnected": "Ya conectado a un canal de voz.", - "noPermission": "ยกLo siento! No tengo permisos para unirme o hablar en su canal de voz.", - "noCreatePermission": "ยกLo siento! No tengo permisos para crear un canal de solicitud de canciones.", - "noPlaySource": "ยกNo se puede encontrar ninguna fuente reproducible!", - "noPlayer": "No se ha encontrado ningรบn reproductor en este servidor.", - "notVote": "ยกEste comando requiere su voto! Escriba `/vote` para obtener mรกs informaciรณn.", - "missingIntents": "Lo siento, este comando no se puede ejecutar porque el bot carece de la intenciรณn de solicitud requerida: `({0})`.", - "languageNotFound": "ยกNo se encontrรณ paquete de idioma! por favor seleccione un paquete de idioma existente.", - "changedLanguage": "Cambiado con รฉxito al paquete de idioma `{0}`.", - "setPrefix": "ยกListo! Mi prefijo en tu servidor ahora es `{0}`. Intenta ejecutar `{1}ping` para probarlo.", - "setDJ": "Establecer el DJ a {0}.", - "setQueue": "Establecer el modo de cola en `{0}`.", - "247": "Ahora tienes `{0}` modo 24/7.", - "bypassVote": "Ahora tienes `{0}` sistema de votaciรณn.", - "setVolume": "Ajustar el volumen a `{0}`%", - "toggleController": "Ahora tienes `{0}` el controlador de mรบsica.", - "toggleDuplicateTrack": "Ahora tienes `{0}` para evitar que se agreguen pistas duplicadas a la cola.", - "toggleControllerMsg": "Ahora tiene `{0}` mensajes del controlador de mรบsica.", - "toggleSilentMsg": "Tienes {0} mensajes silenciosos.", - "settingsMenu": "Configuraciรณn del servidor | {0}", - "settingsTitle": "โค๏ธ Informaciรณn bรกsica:", - "settingsValue": "```Prefijo: {0}\nIdioma: {1}\nHabilitar controlador de mรบsica: {2}\nRol de DJ: @{3}\nBypass de votaciรณn: {4}\n24/7: {5}\nVolumen predeterminado: {6}%\nTiempo de reproducciรณn: {7}```", - "settingsTitle2": "๐Ÿ”— Informaciรณn de la cola:", - "settingsValue2": "```Modo de cola: {0}\nMรกximo de canciones: {1}\nPermitir pistas duplicadas: {2}```", - "settingsTitle3": "๐ŸŽค Informaciรณn del estado de voz:", - "settingsPermTitle": "โœจ Permisos:", - "settingsPermValue": "```{0} Administrador\n{1} Administrar servidor\n{2} Administrar canal\n{3} Administrar mensajes```", - "pingTitle1": "Informaciรณn del bot:", - "pingTitle2": "Informaciรณn del reproductor:", - "pingField1": "```ID de fragmento: {0}/{1}\nLatencia de fragmento: {2:.3f}s {3}\nRegiรณn: {4}```", - "pingField2": "```Nodo: {0} - {1:.3f}s\nReproductores: {2}\nRegiรณn de voz: {3}```", - "addEffect": "plica el efecto `{0}` filtro.", - "clearEffect": "ยกLos efectos de sonido se han borrado!", - "filterTagAlreadyInUse": "ยกEste efecto de sonido ya estรก en uso! Utilice /cleareffect para eliminarlo.", - "playlistViewTitle": "๐Ÿ“œ Todas las listas de reproducciรณn de {0}", - "playlistViewHeaders": "ID:,Tiempo:,Nombre:,Pistas:", - "playlistFooter": "Escriba /playlist play [playlist] para agregar la lista de reproducciรณn a la cola.", - "playlistNotFound": "Lista de reproducciรณn [`{0}`] no encontrada. Escriba /playlist view para ver todas sus listas de reproducciรณn.", - "playlistNotAccess": "ยกLo siento! No estรก autorizado para acceder a esta lista de reproducciรณn.", - "playlistNoTrack": "ยกLo siento! No hay pistas en la lista de reproducciรณn [`{0}`].", - "playlistNotAllow": "Este comando no estรก permitido en listas de reproducciรณn vinculadas y compartidas.", - "playlistPlay": "Se agregรณ la lista de reproducciรณn [`{0}`] con `{1}` canciones a la cola.", - "playlistOverText": "ยกLo siento! El nombre de la lista de reproducciรณn no puede superar los 10 caracteres.", - "playlistSameName": "ยกLo siento! Este nombre no puede ser igual que su nuevo nombre.", - "playlistDeleteError": "No estรก autorizado para eliminar la lista de reproducciรณn predeterminada.", - "playlistRemove": "Ha eliminado la lista de reproducciรณn [`{0}`].", - "playlistSendErrorPlayer": "Lo siento! No puede enviar una invitaciรณn a sรญ mismo.", - "playlistSendErrorBot": "Lo siento! No puede enviar una invitaciรณn a un bot.", - "playlistBelongs": "Lo siento! Esta lista de reproducciรณn pertenece a <@{0}>.", - "playlistShare": "Lo siento! Esta lista de reproducciรณn se ha compartido con `{0}`.", - "playlistSent": "Lo siento! Ya ha enviado una invitaciรณn antes.", - "noPlaylistAcc": "`{0}` no ha creado una cuenta de lista de reproducciรณn.", - "overPlaylistCreation": "ยกNo puede crear mรกs de `{0}` listas de reproducciรณn!", - "playlistExists": "La lista de reproducciรณn [`{0}`] ya existe.", - "playlistNotInvalidUrl": "Ingrese un enlace vรกlido o un enlace pรบblico de lista de reproducciรณn de Spotify o YouTube.", - "playlistCreated": "Ha creado una lista de reproducciรณn llamada `{0}`. Escriba /playlist view para obtener mรกs informaciรณn.", - "playlistRenamed": "Ha cambiado el nombre de `{0}` a `{1}`.", - "playlistLimitTrack": "ยกHa alcanzado el lรญmite! Solo puede agregar `{0}` canciones a su lista de reproducciรณn.", - "playlistPlaylistLink": "No estรก autorizado para usar enlaces de lista de reproducciรณn.", - "playlistStream": "No estรก autorizado para agregar videos de transmisiรณn a su lista de reproducciรณn.", - "playlistPositionNotFound": "No se puede encontrar la posiciรณn {0} en su lista de reproducciรณn [`{1}`]!", - "playlistRemoved": "๐Ÿ‘‹ Se quitรณ {0} de la lista de reproducciรณn de {1} [`{2}`].", - "playlistClear": "Ha borrado con รฉxito su lista de reproducciรณn [`{0}`].", - "playlistView": "Visor de lista de reproducciรณn", - "playlistViewDesc": "```Nombre | ID: {0} | {1}\nTotal de pistas: {2}\nPropietario: {3}\nTipo: {4}\n```", - "playlistViewPermsValue": "๐Ÿ“– Leer: โœ“ โœ๐Ÿฝ Escribir: {0} ๐Ÿ—‘๏ธ Eliminar: {1}", - "playlistViewPermsValue2": "๐Ÿ“– Leer: {0}", - "playlistViewTrack": "Pistas", - "playlistViewPage": "Pรกgina: {0}/{1} | Duraciรณn total: {2}", - "inboxFull": "ยกLo siento! La bandeja de entrada de {0} estรก llena.", - "inboxNoMsg": "No hay mensajes en su bandeja de entrada.", - "invitationSent": "Se ha enviado una invitaciรณn a {0}.", - "notInChannel": "{0}, debes estar en {1} para usar comandos de voz. ยกVuelve a unirte si estรกs en voz!", - "noTrackPlaying": "Actualmente no hay canciones reproduciรฉndose.", - "noTrackFound": "No se encontraron canciones con esa consulta. Proporcione una URL vรกlida.", - "noLinkSupport": "ยกEl comando de bรบsqueda no admite enlaces!", - "voted": "ยกHas votado!", - "missingPosPerm": "Solo el DJ o los administradores pueden cambiar la posiciรณn.", - "missingModePerm": "Solo el DJ o los administradores pueden cambiar el modo de bucle.", - "missingQueuePerm": "Solo el DJ o los administradores pueden eliminar una canciรณn de la cola.", - "missingAutoPlayPerm": "ยกSolo el DJ o los administradores pueden habilitar o deshabilitar el modo de reproducciรณn automรกtica!", - "missingFunctionPerm": "Solo el DJ o los administradores pueden usar esta funciรณn.", - "timeFormatError": "Formato de tiempo incorrecto. Ejemplo: 2:42 o 12:39:31", - "lyricsNotFound": "No se encontraron letras. Escriba /lyrics para buscar las letras.", - "missingTrackInfo": "Falta informaciรณn de la canciรณn.", - "noVoiceChannel": "ยกCanal de voz no encontrado!", - "playlistAddError": "No estรก autorizado para agregar videos de transmisiรณn a su lista de reproducciรณn.", - "playlistAddError2": "Hubo un problema al agregar canciones a la lista de reproducciรณn.", - "playlistLimited": "ยกHa alcanzado el lรญmite! Solo puede agregar {0} canciones a su lista de reproducciรณn.", - "playlistRepeated": "ยกYa hay una canciรณn igual en su lista de reproducciรณn!", - "playlistAdded": "โค๏ธ Agregado **{0}** a la lista de reproducciรณn de {1} [`{2}`]!", - "playerDropdown": "Seleccione una canciรณn para saltar a ...", - "playerFilter": "Seleccione un filtro para aplicar ...", - "buttonBack": "Atrรกs", - "buttonPause": "Pausa", - "buttonResume": "Reanudar", - "buttonSkip": "Saltar", - "buttonLeave": "Salir", - "buttonLoop": "Bucle", - "buttonVolumeUp": "Subir Volumen", - "buttonVolumeDown": "Bajar Volumen", - "buttonVolumeMute": "Silenciar", - "buttonVolumeUnmute": "Activar Sonido", - "buttonAutoPlay": "Reproducciรณn Automรกtica", - "buttonShuffle": "Aleatorio", - "buttonForward": "Adelante", - "buttonRewind": "Atrรกs", - "buttonLyrics": "Letras", - "nowplayingDesc": "**Reproduciendo ahora:**\n```{0}```", - "nowplayingField": "A continuaciรณn:", - "nowplayingLink": "Escucha en {0}", - "connect": "Conectado a {0}", - "live": "EN VIVO", - "playlistLoad": " ๐ŸŽถ Se agregรณ la lista de reproducciรณn {0} con {1} canciones a la cola.", - "trackLoad": "Se agregรณ **[{0}](<{1}>)** de {2} ({3}) para comenzar a reproducir.\n", - "trackLoad_pos": "Se agregรณ **[{0}](<{1}>)** de {2} ({3}) a la cola en la posiciรณn {4}\n", - "searchTitle": "Consulta de bรบsqueda: {0}", - "searchDesc": "โžฅ Plataforma: {0} **{1}**\nโžฅ Resultados: **{2}**\n\n{3}", - "searchWait": "Seleccione la canciรณn que desea agregar a la cola.", - "searchTimeout": "La bรบsqueda ha expirado. Por favor, intรฉntelo de nuevo mรกs tarde.", - "searchSuccess": "Canciรณn agregada a la cola.", - "queueTitle": "Cola de prรณximas canciones:", - "historyTitle": "Cola de historial:", - "viewTitle": "Cola de mรบsica", - "viewDesc": "**Reproduciendo ahora: [Haga clic aquรญ]({0}) โฎฏ**\n{1}", - "viewFooter": "Pรกgina: {0}/{1} | Duraciรณn total: {2}", - "pauseError": "El reproductor ya estรก en pausa.", - "pauseVote": "{0} ha votado para pausar la canciรณn. [{1}/{2}]", - "paused": "`{0}` ha pausado el reproductor.", - "resumeError": "El reproductor no estรก en pausa.", - "resumeVote": "{0} ha votado para reanudar la canciรณn. [{1}/{2}]", - "resumed": "`{0}` ha reanudado el reproductor.", - "shuffleError": "Agregue mรกs canciones a la cola antes de barajar.", - "shuffleVote": "{0} ha votado para barajar la cola. [{1}/{2}]", - "shuffled": "La cola ha sido barajada.", - "skipError": "No hay canciones para saltar.", - "skipVote": "{0} ha votado para saltar la canciรณn. [{1}/{2}]", - "skipped": "`{0}` ha saltado la canciรณn.", - "backVote": "{0} ha votado para volver a la canciรณn anterior. [{1}/{2}]", - "backed": "`{0}` ha vuelto a la canciรณn anterior.", - "leaveVote": "{0} ha votado para detener el reproductor. [{1}/{2}]", - "left": "`{0}` ha detenido el reproductor.", - "seek": "El reproductor se estableciรณ en **{0}**", - "repeat": "El modo de repeticiรณn se ha establecido en `{0}`", - "cleared": "Se borraron todas las canciones de `{0}`", - "removed": "Se eliminaron `{0}` canciones de la cola.", - "forward": "Avanzar el reproductor a **{0}**", - "rewind": "Rebobinar el reproductor a **{0}**", - "replay": "Reproduciendo de nuevo la canciรณn actual.", - "swapped": "Se intercambiaron `{0}` y `{1}`", - "moved": "Se moviรณ `{0}` a `{1}`", - "autoplay": "El modo de reproducciรณn automรกtica es ahora **{0}**", - "notdj": "No eres el DJ. El DJ actual es {0}.", - "djToMe": "No puedes transferir el rol de DJ a ti mismo o a un bot.", - "djNotInChannel": "`{0}` no estรก en el canal de voz.", - "djswap": "Has transferido el rol de DJ a `{0}`.", - "voicelinkQueueFull": "Lo siento, ยกha alcanzado el mรกximo de `{0}` canciones en la cola!", - "voicelinkOutofList": "ยกProporcione un รญndice de pista vรกlido!", - "voicelinkDuplicateTrack": "Lo siento, esta canciรณn ya estรก en la cola.", - "decodeError": "ยกAlgo saliรณ mal al decodificar el archivo!", - "invalidStartTime": "Tiempo de inicio invรกlido! El tiempo debe estar entre `00:00` y `{0}`.", - "invalidEndTime": "Tiempo de finalizaciรณn invรกlido! El tiempo debe estar entre `00:00` y `{0}`.", - "invalidTimeOrder": "El tiempo final no puede ser menor o igual que el tiempo de inicio.", - "setStageAnnounceTemplate": "ยกHecho! A partir de ahora, el estado de voz como el que tienes ahora se nombrarรก segรบn tu plantilla. Deberรญas verlo actualizarse en unos segundos.", - "createSongRequestChannel": "ยกSe ha creado un canal de solicitudes de canciones ({0})! Puedes empezar a solicitar cualquier canciรณn por nombre o URL en ese canal, sin necesidad de usar el prefijo del bot." -} \ No newline at end of file + "common": { + "errors": { + "unknown": "โš ๏ธ ยกAlgo saliรณ mal al ejecutar el comando! Por favor, intente de nuevo mรกs tarde o รบnase a nuestro servidor de Discord para obtener mรกs soporte.", + "missingIntents": "Lo siento, este comando no se puede ejecutar porque el bot carece de la intenciรณn de solicitud requerida: `({0})`." + }, + "status": { + "enabled": "habilitado", + "disabled": "deshabilitado", + "live": "EN VIVO" + } + }, + "player": { + "errors": { + "nodeReconnect": "ยกPor favor, intรฉntelo de nuevo! Despuรฉs de que el nodo se reconecte.", + "noPlaySource": "ยกNo se puede encontrar ninguna fuente reproducible!", + "noPlayer": "No se ha encontrado ningรบn reproductor en este servidor.", + "noTrackPlaying": "Actualmente no hay canciones reproduciรฉndose.", + "noTrackFound": "No se encontraron canciones con esa consulta. Proporcione una URL vรกlida." + }, + "dropdown": { + "trackSelect": "Seleccione una canciรณn para saltar a ...", + "filterSelect": "Seleccione un filtro para aplicar ..." + }, + "buttons": { + "back": "Atrรกs", + "pause": "Pausa", + "resume": "Reanudar", + "skip": "Saltar", + "leave": "Salir", + "loop": "Bucle", + "volumeUp": "Subir Volumen", + "volumeDown": "Bajar Volumen", + "volumeMute": "Silenciar", + "volumeUnmute": "Activar Sonido", + "autoPlay": "Reproducciรณn Automรกtica", + "shuffle": "Aleatorio", + "forward": "Adelante", + "rewind": "Atrรกs", + "lyrics": "Letras" + }, + "playback": { + "nowplayingDesc": "**Reproduciendo ahora:**\n```{0}```", + "nowplayingField": "A continuaciรณn:", + "nowplayingLink": "Escucha en {0}", + "playlistLoad": " ๐ŸŽถ Se agregรณ la lista de reproducciรณn {0} con {1} canciones a la cola.", + "trackLoad": "Se agregรณ **[{0}](<{1}>)** de {2} ({3}) para comenzar a reproducir.\n", + "trackLoadPos": "Se agregรณ **[{0}](<{1}>)** de {2} ({3}) a la cola en la posiciรณn {4}\n" + }, + "controls": { + "pause": { + "error": "El reproductor ya estรก en pausa.", + "vote": "{0} ha votado para pausar la canciรณn. [{1}/{2}]", + "success": "`{0}` ha pausado el reproductor." + }, + "resume": { + "error": "El reproductor no estรก en pausa.", + "vote": "{0} ha votado para reanudar la canciรณn. [{1}/{2}]", + "success": "`{0}` ha reanudado el reproductor." + }, + "shuffle": { + "error": "Agregue mรกs canciones a la cola antes de barajar.", + "vote": "{0} ha votado para barajar la cola. [{1}/{2}]", + "success": "La cola ha sido barajada." + }, + "skip": { + "error": "No hay canciones para saltar.", + "vote": "{0} ha votado para saltar la canciรณn. [{1}/{2}]", + "success": "`{0}` ha saltado la canciรณn." + }, + "back": { + "vote": "{0} ha votado para volver a la canciรณn anterior. [{1}/{2}]", + "success": "`{0}` ha vuelto a la canciรณn anterior." + }, + "leave": { + "vote": "{0} ha votado para detener el reproductor. [{1}/{2}]", + "success": "`{0}` ha detenido el reproductor." + }, + "seek": "El reproductor se estableciรณ en **{0}**", + "repeat": "El modo de repeticiรณn se ha establecido en `{0}`", + "forward": "Avanzar el reproductor a **{0}**", + "rewind": "Rebobinar el reproductor a **{0}**", + "replay": "Reproduciendo de nuevo la canciรณn actual.", + "autoplay": "El modo de reproducciรณn automรกtica es ahora **{0}**" + } + }, + "voice": { + "connection": { + "noChannel": "No hay canal de voz al que conectarse. Por favor, proporcione uno o รบnase a uno.", + "alreadyConnected": "Ya conectado a un canal de voz.", + "noPermission": "ยกLo siento! No tengo permisos para unirme o hablar en su canal de voz.", + "notInChannel": "{0}, debes estar en {1} para usar comandos de voz. ยกVuelve a unirte si estรกs en voz!", + "connect": "Conectado a {0}" + }, + "stageChannel": { + "setAnnounceTemplate": "ยกHecho! A partir de ahora, el estado de voz como el que tienes ahora se nombrarรก segรบn tu plantilla. Deberรญas verlo actualizarse en unos segundos." + } + }, + "permissions": { + "noCreatePermission": "ยกLo siento! No tengo permisos para crear un canal de solicitud de canciones.", + "missingPosition": "Solo el DJ o los administradores pueden cambiar la posiciรณn.", + "missingMode": "Solo el DJ o los administradores pueden cambiar el modo de bucle.", + "missingQueue": "Solo el DJ o los administradores pueden eliminar una canciรณn de la cola.", + "missingAutoPlay": "ยกSolo el DJ o los administradores pueden habilitar o deshabilitar el modo de reproducciรณn automรกtica!", + "missingFunction": "Solo el DJ o los administradores pueden usar esta funciรณn.", + "notDj": "No eres el DJ. El DJ actual es {0}.", + "djToSelf": "No puedes transferir el rol de DJ a ti mismo o a un bot.", + "djNotInChannel": "`{0}` no estรก en el canal de voz.", + "djSwapped": "Has transferido el rol de DJ a `{0}`." + }, + "settings": { + "actions": { + "languageNotFound": "ยกNo se encontrรณ paquete de idioma! por favor seleccione un paquete de idioma existente.", + "languageChanged": "Cambiado con รฉxito al paquete de idioma `{0}`.", + "prefixSet": "ยกListo! Mi prefijo en tu servidor ahora es `{0}`. Intenta ejecutar `{1}ping` para probarlo.", + "djSet": "Establecer el DJ a {0}.", + "queueModeSet": "Establecer el modo de cola en `{0}`.", + "mode247": "Ahora tienes `{0}` modo 24/7.", + "bypassVote": "Ahora tienes `{0}` sistema de votaciรณn.", + "volumeSet": "Ajustar el volumen a `{0}`%", + "controllerToggled": "Ahora tienes `{0}` el controlador de mรบsica.", + "duplicateTrackToggled": "Ahora tienes `{0}` para evitar que se agreguen pistas duplicadas a la cola.", + "controllerMsgToggled": "Ahora tiene `{0}` mensajes del controlador de mรบsica.", + "silentMsgToggled": "Tienes {0} mensajes silenciosos." + }, + "menu": "Configuraciรณn del servidor | {0}", + "basic": { + "title": "โค๏ธ Informaciรณn bรกsica:", + "value": "```Prefijo: {0}\nIdioma: {1}\nHabilitar controlador de mรบsica: {2}\nRol de DJ: @{3}\nBypass de votaciรณn: {4}\n24/7: {5}\nVolumen predeterminado: {6}%\nTiempo de reproducciรณn: {7}```" + }, + "queue": { + "title": "๐Ÿ”— Informaciรณn de la cola:", + "value": "```Modo de cola: {0}\nMรกximo de canciones: {1}\nPermitir pistas duplicadas: {2}```" + }, + "voice": { + "title": "๐ŸŽค Informaciรณn del estado de voz:" + }, + "permissions": { + "title": "โœจ Permisos:", + "value": "```{0} Administrador\n{1} Administrar servidor\n{2} Administrar canal\n{3} Administrar mensajes```" + } + }, + "ping": { + "title1": "Informaciรณn del bot:", + "title2": "Informaciรณn del reproductor:", + "field1": "```ID de fragmento: {0}/{1}\nLatencia de fragmento: {2:.3f}s {3}\nRegiรณn: {4}```", + "field2": "```Nodo: {0} - {1:.3f}s\nReproductores: {2}\nRegiรณn de voz: {3}```" + }, + "effects": { + "added": "plica el efecto `{0}` filtro.", + "cleared": "ยกLos efectos de sonido se han borrado!", + "tagInUse": "ยกEste efecto de sonido ya estรก en uso! Utilice /cleareffect para eliminarlo." + }, + "playlist": { + "view": { + "title": "Todas las listas de reproducciรณn de {0}", + "headers": "ID:,Tiempo:,Nombre:,Pistas:", + "footer": "Escriba /playlist play [playlist] para agregar la lista de reproducciรณn a la cola.", + "detailTitle": "Visor de lista de reproducciรณn", + "detailDesc": "```Nombre: {0} [{1}]\nTotal de pistas: {2}\nPropietario: {3}\nTipo: {4}\n```", + "permsValue": "๐Ÿ“– Leer: โœ“ โœ๐Ÿฝ Escribir: {0} ๐Ÿ—‘๏ธ Eliminar: {1}", + "permsValue2": "๐Ÿ“– Leer: {0}", + "trackList": "Pistas", + "footer2": "Duraciรณn total: {0}", + "playlistSelect": "Selecciona una lista de reproducciรณn para ver ..." + }, + "errors": { + "notFound": "Lista de reproducciรณn [`{0}`] no encontrada. Escriba /playlist view para ver todas sus listas de reproducciรณn.", + "noAccess": "ยกLo siento! No estรก autorizado para acceder a esta lista de reproducciรณn.", + "noTrack": "ยกLo siento! No hay pistas en la lista de reproducciรณn [`{0}`].", + "notAllowed": "Este comando no estรก permitido en listas de reproducciรณn vinculadas y compartidas.", + "nameOverLimit": "ยกLo siento! El nombre de la lista de reproducciรณn no puede superar los 10 caracteres.", + "sameName": "ยกLo siento! Este nombre no puede ser igual que su nuevo nombre.", + "deleteDefault": "No estรก autorizado para eliminar la lista de reproducciรณn predeterminada.", + "limitReached": "ยกNo puede crear mรกs de `{0}` listas de reproducciรณn!", + "exists": "La lista de reproducciรณn [`{0}`] ya existe.", + "invalidUrl": "Ingrese un enlace vรกlido o un enlace pรบblico de lista de reproducciรณn de Spotify o YouTube.", + "trackLimitReached": "ยกHa alcanzado el lรญmite! Solo puede agregar `{0}` canciones a su lista de reproducciรณn.", + "playlistLinkNotAllowed": "No estรก autorizado para usar enlaces de lista de reproducciรณn.", + "streamNotAllowed": "No estรก autorizado para agregar videos de transmisiรณn a su lista de reproducciรณn.", + "positionNotFound": "No se puede encontrar la posiciรณn {0} en su lista de reproducciรณn [`{1}`]!", + "streaming": "No estรก autorizado para agregar videos de transmisiรณn a su lista de reproducciรณn.", + "track": "Hubo un problema al agregar canciones a la lista de reproducciรณn.", + "trackLimited": "ยกHa alcanzado el lรญmite! Solo puede agregar {0} canciones a su lista de reproducciรณn.", + "trackRepeated": "ยกYa hay una canciรณn igual en su lista de reproducciรณn!" + }, + "actions": { + "play": "Se agregรณ la lista de reproducciรณn [`{0}`] con `{1}` canciones a la cola.", + "removed": "Ha eliminado la lista de reproducciรณn [`{0}`].", + "created": "Ha creado una lista de reproducciรณn llamada `{0}`. Escriba /playlist view para obtener mรกs informaciรณn.", + "renamed": "Ha cambiado el nombre de `{0}` a `{1}`.", + "trackRemoved": "๐Ÿ‘‹ Se quitรณ {0} de la lista de reproducciรณn de {1} [`{2}`].", + "cleared": "Ha borrado con รฉxito su lista de reproducciรณn [`{0}`].", + "trackAdded": "โค๏ธ Agregado **{0}** a la lista de reproducciรณn de {1} [`{2}`]!" + }, + "sharing": { + "sendErrorPlayer": "Lo siento! No puede enviar una invitaciรณn a sรญ mismo.", + "sendErrorBot": "Lo siento! No puede enviar una invitaciรณn a un bot.", + "belongs": "Lo siento! Esta lista de reproducciรณn pertenece a <@{0}>.", + "alreadyShared": "Lo siento! Esta lista de reproducciรณn se ha compartido con `{0}`.", + "alreadySent": "Lo siento! Ya ha enviado una invitaciรณn antes.", + "noAccount": "`{0}` no ha creado una cuenta de lista de reproducciรณn.", + "invitationSent": "Se ha enviado una invitaciรณn a {0}." + }, + "inbox": { + "full": "ยกLo siento! La bandeja de entrada de {0} estรก llena.", + "noMessages": "No hay mensajes en su bandeja de entrada." + } + }, + "search": { + "noLinkSupport": "ยกEl comando de bรบsqueda no admite enlaces!", + "title": "Consulta de bรบsqueda: {0}", + "desc": "โžฅ Plataforma: {0} **{1}**\nโžฅ Resultados: **{2}**\n\n{3}", + "wait": "Seleccione la canciรณn que desea agregar a la cola.", + "success": "Canciรณn agregada a la cola." + }, + "voting": { + "voted": "ยกHas votado!" + }, + "time": { + "formatError": "Formato de tiempo incorrecto. Ejemplo: 2:42 o 12:39:31", + "invalidStartTime": "Tiempo de inicio invรกlido! El tiempo debe estar entre `00:00` y `{0}`.", + "invalidEndTime": "Tiempo de finalizaciรณn invรกlido! El tiempo debe estar entre `00:00` y `{0}`." + }, + "lyrics": { + "notFound": "No se encontraron letras. Escriba /lyrics para buscar las letras." + }, + "queue": { + "management": { + "title": "Cola de prรณximas canciones:", + "cleared": "Se borraron todas las canciones de `{0}`", + "removed": "Se eliminaron `{0}` canciones de la cola.", + "swapped": "Se intercambiaron `{0}` y `{1}`", + "moved": "Se moviรณ `{0}` a `{1}`" + }, + "view": { + "historyTitle": "Cola de historial:", + "title": "Cola de mรบsica", + "desc": "**Reproduciendo ahora: [Haga clic aquรญ]({0}) โฎฏ**\n{1}" + }, + "errors": { + "queueFull": "Lo siento, ยกha alcanzado el mรกximo de `{0}` canciones en la cola!", + "outOfList": "ยกProporcione un รญndice de pista vรกlido!", + "duplicateTrack": "Lo siento, esta canciรณn ya estรก en la cola." + } + }, + "songRequest": { + "channelCreated": "ยกSe ha creado un canal de solicitudes de canciones ({0})! Puedes empezar a solicitar cualquier canciรณn por nombre o URL en ese canal, sin necesidad de usar el prefijo del bot." + }, + "pagination": { + "prev": "Anterior", + "next": "Siguiente", + "play": "Reproducir", + "share": "Compartir", + "export": "Exportar", + "delete": "Eliminar", + "page": { + "title": "Ingrese el nรบmero de pรกgina", + "field": "Nรบmero de pรกgina" + } + } +} diff --git a/langs/FR.json b/langs/FR.json index fb1df9e..90d6441 100644 --- a/langs/FR.json +++ b/langs/FR.json @@ -1,172 +1,262 @@ { - "unknownException": "โš ๏ธ Une erreur est survenue lors de l'exรฉcution de la commande ! Veuillez rรฉessayer plus tard ou rejoignez notre serveur Discord pour plus d'assistance.", - "enabled": "activรฉ", - "disabled": "dรฉsactivรฉ", - "nodeReconnect": "Veuillez rรฉessayer aprรจs la reconnexion du node.", - "noChannel": "Aucun salon vocal pour se connecter. Veuillez en fournir un ou rejoindre un salon vocal.", - "alreadyConnected": "Dรฉjร  connectรฉ ร  un salon vocal.", - "noPermission": "Dรฉsolรฉ ! Je n'ai pas les permissions pour rejoindre ou parler dans votre salon vocal.", - "noCreatePermission": "Dรฉsolรฉ ! Je n'ai pas les permissions pour crรฉer un salon de demande de chansons.", - "noPlaySource": "Impossible de trouver une source ร  lire !", - "noPlayer": "Aucun lecteur n'a รฉtรฉ trouvรฉ sur ce serveur.", - "notVote": "Cette commande nรฉcessite votre vote ! Tapez `/vote` pour en savoir plus.", - "missingIntents": "Dรฉsolรฉ, cette commande ne peut pas รชtre exรฉcutรฉe car le bot n'a pas l'intent requis : `({0})`.", - "languageNotFound": "Aucun pack de langue trouvรฉ ! Veuillez sรฉlectionner un pack de langue existant.", - "changedLanguage": "Changement rรฉussi vers le pack de langue `{0}`.", - "setPrefix": "C'est fait ! Mon prรฉfixe dans votre serveur est dรฉsormais `{0}`. Essayez de lancer `{1}ping` pour tester.", - "setDJ": "Le DJ a รฉtรฉ dรฉfini sur {0}.", - "setQueue": "Le mode file d'attente est dรฉfini sur `{0}`.", - "247": "Le mode 24/7 est maintenant `{0}`.", - "bypassVote": "Vous avez le systรจme de vote `{0}`.", - "setVolume": "Le volume est rรฉglรฉ ร  `{0}`%", - "toggleController": "Vous avez maintenant `{0}` le contrรดleur de musique.", - "toggleDuplicateTrack": "Vous avez maintenant `{0}` la prรฉvention des morceaux en double.", - "toggleControllerMsg": "Vous avez maintenant `{0}` les messages du contrรดleur musical.", - "toggleSilentMsg": "Vous avez {0} messages silencieux.", - "settingsMenu": "Paramรจtres du serveur | {0}", - "settingsTitle": "โค๏ธ Infos de base :", - "settingsValue": "```Prรฉfixe : {0}\nLangue : {1}\nContrรดleur musique : {2}\nRรดle DJ : @{3}\nVote Bypass : {4}\n24/7 : {5}\nVolume par dรฉfaut : {6}%\nTemps de lecture : {7}```", - "settingsTitle2": "๐Ÿ”— Infos sur la file :", - "settingsValue2": "```Mode de file : {0}\nMaximum de chansons : {1}\nPiste dupliquรฉe : {2}```", - "settingsTitle3": "๐ŸŽค Infos sur l'รฉtat vocal :", - "settingsPermTitle": "โœจ Permissions :", - "settingsPermValue": "```{0} Administrateur\n{1} Gรฉrer_serveur\n{2} Gรฉrer_salons\n{3} Gรฉrer_messages```", - "pingTitle1": "Infos Bot :", - "pingTitle2": "Infos Lecteur :", - "pingField1": "```Shard ID : {0}/{1}\nLatence Shard : {2:.3f}s {3}\nRรฉgion : {4}```", - "pingField2": "```Node : {0} - {1:.3f}s\nLecteurs : {2}\nRรฉgion vocale : {3}```", - "addEffect": "Effet `{0}` appliquรฉ.", - "clearEffect": "Les effets sonores ont รฉtรฉ supprimรฉs !", - "filterTagAlreadyInUse": "Cet effet sonore est dรฉjร  utilisรฉ ! Veuillez utiliser /cleareffect pour le retirer.", - "playlistViewTitle": "๐Ÿ“œ Toutes les playlists de {0}", - "playlistViewHeaders": "ID:,Durรฉe:,Nom:,Morceaux:", - "playlistFooter": "Tapez /playlist play [playlist] pour ajouter une playlist ร  la file.", - "playlistNotFound": "Playlist [`{0}`] introuvable. Tapez /playlist view pour voir toutes vos playlists.", - "playlistNotAccess": "Dรฉsolรฉ ! Vous n'รชtes pas autorisรฉ ร  accรฉder ร  cette playlist !", - "playlistNoTrack": "Dรฉsolรฉ ! Il n'y a aucun morceau dans la playlist [`{0}`].", - "playlistNotAllow": "Cette commande n'est pas autorisรฉe sur les playlists liรฉes ou partagรฉes.", - "playlistPlay": "La playlist [`{0}`] avec `{1}` chansons a รฉtรฉ ajoutรฉe ร  la file.", - "playlistOverText": "Dรฉsolรฉ ! Le nom de la playlist ne peut pas dรฉpasser 10 caractรจres.", - "playlistSameName": "Dรฉsolรฉ ! Veuillez choisir un nouveau nom pour la playlist.", - "playlistDeleteError": "Vous n'รชtes pas autorisรฉ ร  supprimer la playlist par dรฉfaut.", - "playlistRemove": "Vous avez supprimรฉ la playlist [`{0}`].", - "playlistSendErrorPlayer": "Dรฉsolรฉ ! Vous ne pouvez pas vous envoyer une invitation ร  vous-mรชme.", - "playlistSendErrorBot": "Dรฉsolรฉ ! Vous ne pouvez pas envoyer une invitation ร  un bot.", - "playlistBelongs": "Dรฉsolรฉ ! Cette playlist appartient ร  <@{0}>.", - "playlistShare": "Dรฉsolรฉ ! Cette playlist a รฉtรฉ partagรฉe avec {0}.", - "playlistSent": "Dรฉsolรฉ ! Vous avez dรฉjร  envoyรฉ une invitation ร  cet utilisateur auparavant.", - "noPlaylistAcc": "{0} n'a pas crรฉรฉ de compte playlist.", - "overPlaylistCreation": "Vous ne pouvez pas crรฉer plus de `{0}` playlists !", - "playlistExists": "La playlist [`{0}`] existe dรฉjร .", - "playlistNotInvalidUrl": "Veuillez entrer un lien valide de playlist publique Spotify ou YouTube.", - "playlistCreated": "Vous avez crรฉรฉ `{0}` playlists. Tapez /playlist view pour plus d'infos.", - "playlistRenamed": "Vous avez renommรฉ `{0}` en `{1}`.", - "playlistLimitTrack": "Vous avez atteint la limite ! Vous ne pouvez ajouter que `{0}` chansons ร  votre playlist.", - "playlistPlaylistLink": "Vous n'รชtes pas autorisรฉ ร  utiliser des liens de playlist.", - "playlistStream": "Vous n'รชtes pas autorisรฉ ร  ajouter des vidรฉos en streaming ร  votre playlist.", - "playlistPositionNotFound": "Impossible de trouver la position `{0}` dans votre playlist [`{1}`] !", - "playlistRemoved": "๐Ÿ‘‹ **{0}** a รฉtรฉ retirรฉ de la playlist [`{2}`] de {1}.", - "playlistClear": "Vous avez vidรฉ avec succรจs votre playlist [`{0}`].", - "playlistView": "Visualiseur de playlist", - "playlistViewDesc": "```Nom | ID : {0} | {1}\nTotal Morceaux : {2}\nPropriรฉtaire : {3}\nType : {4}\n```", - "playlistViewPermsValue": "๐Ÿ“– Lecture : โœ“ โœ๐Ÿฝ ร‰criture : {0} ๐Ÿ—‘๏ธ Suppression : {1}", - "playlistViewPermsValue2": "๐Ÿ“– Lecture : {0}", - "playlistViewTrack": "Morceaux", - "playlistViewPage": "Page : {0}/{1} | Durรฉe totale : {2}", - "inboxFull": "Dรฉsolรฉ ! La boรฎte de rรฉception de {0} est pleine.", - "inboxNoMsg": "Aucun message dans votre boรฎte de rรฉception.", - "invitationSent": "Invitation envoyรฉe ร  {0}.", - "notInChannel": "{0}, vous devez รชtre dans {1} pour utiliser les commandes vocales. Veuillez rejoindre ร  nouveau si vous รชtes dรฉjร  en vocal !", - "noTrackPlaying": "Aucune chanson n'est en cours de lecture pour le moment", - "noTrackFound": "Aucune chanson trouvรฉe pour cette requรชte ! Veuillez fournir une URL valide.", - "noLinkSupport": "La commande de recherche ne prend pas en charge les liens !", - "voted": "Vous avez votรฉ !", - "missingPosPerm": "Seul le DJ ou les admins peuvent changer la position.", - "missingModePerm": "Seul le DJ ou les admins peuvent changer le mode de boucle.", - "missingQueuePerm": "Seul le DJ ou les admins peuvent retirer un morceau de la file.", - "missingAutoPlayPerm": "Seul le DJ ou les admins peuvent activer/dรฉsactiver l'autoplay !", - "missingFunctionPerm": "Seul le DJ ou un admin peut utiliser cette fonction.", - "timeFormatError": "Format de temps incorrect. Exemple : 2:42 ou 12:39:31", - "lyricsNotFound": "Paroles non trouvรฉes. Tapez /lyrics pour chercher les paroles.", - "missingTrackInfo": "Certaines informations sur le morceau sont manquantes.", - "noVoiceChannel": "Aucun salon vocal trouvรฉ !", - "playlistAddError": "Vous n'รชtes pas autorisรฉ ร  ajouter des vidรฉos en streaming ร  votre playlist !", - "playlistAddError2": "Problรจme lors de l'ajout de morceaux ร  la playlist !", - "playlistLimited": "Vous avez atteint la limite ! Vous ne pouvez ajouter que {0} chansons ร  votre playlist.", - "playlistRepeated": "Ce morceau est dรฉjร  dans votre playlist !", - "playlistAdded": "โค๏ธ **{0}** ajoutรฉ ร  la playlist [`{2}`] de {1} !", - "playerDropdown": "Sรฉlectionnez une chanson vers laquelle passer...", - "playerFilter": "Sรฉlectionnez un filtre ร  appliquer...", - "buttonBack": "Prรฉcรฉdent", - "buttonPause": "Pause", - "buttonResume": "Reprendre", - "buttonSkip": "Passer", - "buttonLeave": "Quitter", - "buttonLoop": "Boucle", - "buttonVolumeUp": "Volume +", - "buttonVolumeDown": "Volume -", - "buttonVolumeMute": "Muet", - "buttonVolumeUnmute": "Son activรฉ", - "buttonAutoPlay": "Autoplay", - "buttonShuffle": "Mรฉlanger", - "buttonForward": "Avancer", - "buttonRewind": "Rembobiner", - "buttonLyrics": "Paroles", - "nowplayingDesc": "**Lecture en cours :**\n```{0}```", - "nowplayingField": "ร€ suivre :", - "nowplayingLink": "ร‰couter sur {0}", - "connect": "Connectรฉ ร  {0}", - "live": "EN DIRECT", - "playlistLoad": " ๐ŸŽถ La playlist **{0}** avec `{1}` chansons a รฉtรฉ ajoutรฉe ร  la file.", - "trackLoad": "**[{0}](<{1}>)** de **{2}** (`{3}`) a รฉtรฉ ajoutรฉe et commencera ร  jouer.\n", - "trackLoad_pos": "**[{0}](<{1}>)** de **{2}** (`{3}`) a รฉtรฉ ajoutรฉe ร  la file ร  la position **{4}**\n", - "searchTitle": "Recherche : {0}", - "searchDesc": "โžฅ Plateforme : {0} **{1}**\nโžฅ Rรฉsultats : **{2}**\n\n{3}", - "searchWait": "Choisissez la chanson ร  ajouter ร  la file.", - "searchTimeout": "Recherche expirรฉe. Veuillez rรฉessayer plus tard.", - "searchSuccess": "Chanson ajoutรฉe ร  la file.", - "queueTitle": "File ร  venir :", - "historyTitle": "Historique :", - "viewTitle": "File de musiques", - "viewDesc": "**Lecture en cours : [Cliquez ici]({0}) โฎฏ**\n{1}", - "viewFooter": "Page : {0}/{1} | Durรฉe totale : {2}", - "pauseError": "Le lecteur est dรฉjร  en pause.", - "pauseVote": "{0} a votรฉ pour mettre la chanson en pause. [{1}/{2}]", - "paused": "`{0}` a mis le lecteur en pause.", - "resumeError": "Le lecteur n'est pas en pause.", - "resumeVote": "{0} a votรฉ pour reprendre la chanson. [{1}/{2}]", - "resumed": "`{0}` a repris le lecteur.", - "shuffleError": "Ajoutez plus de chansons ร  la file avant de mรฉlanger.", - "shuffleVote": "{0} a votรฉ pour mรฉlanger la file. [{1}/{2}]", - "shuffled": "La file a รฉtรฉ mรฉlangรฉe.", - "skipError": "Aucune chanson vers laquelle passer.", - "skipVote": "{0} a votรฉ pour passer la chanson. [{1}/{2}]", - "skipped": "`{0}` a passรฉ la chanson.", - "backVote": "{0} a votรฉ pour passer ร  la chanson prรฉcรฉdente. [{1}/{2}]", - "backed": "`{0}` est en train de passer ร  la chanson prรฉcรฉdente.", - "leaveVote": "{0} a votรฉ pour arrรชter le lecteur. [{1}/{2}]", - "left": "`{0}` a arrรชtรฉ le lecteur.", - "seek": "Le lecteur a รฉtรฉ placรฉ ร  **{0}**", - "repeat": "Le mode de rรฉpรฉtition a รฉtรฉ rรฉglรฉ sur `{0}`", - "cleared": "Tous les morceaux dans `{0}` ont รฉtรฉ supprimรฉs", - "removed": "`{0}` morceaux ont รฉtรฉ retirรฉs de la file.", - "forward": "Le lecteur a avancรฉ ร  **{0}**", - "rewind": "Le lecteur a รฉtรฉ rembobinรฉ ร  **{0}**", - "replay": "Relecture de la chanson actuelle.", - "swapped": "`{0}` et `{1}` ont รฉtรฉ รฉchangรฉs", - "moved": "`{0}` a รฉtรฉ dรฉplacรฉ vers `{1}`", - "autoplay": "Le mode autoplay est maintenant **{0}**", - "notdj": "Vous n'รชtes pas DJ. Le DJ actuel est {0}.", - "djToMe": "Impossible de transfรฉrer le rรดle de DJ ร  vous-mรชme ou ร  un bot.", - "djNotInChannel": "`{0}` n'est pas dans le salon vocal.", - "djswap": "Vous avez transfรฉrรฉ le rรดle de DJ ร  `{0}`.", - "voicelinkQueueFull": "Dรฉsolรฉ, vous avez atteint le maximum de `{0}` morceaux dans la file !", - "voicelinkOutofList": "Veuillez fournir un index de morceau valide !", - "voicelinkDuplicateTrack": "Dรฉsolรฉ, ce morceau est dรฉjร  dans la file.", - "decodeError": "Une erreur s'est produite lors du dรฉcodage du fichier !", - "invalidStartTime": "Heure de dรฉbut invalide, doit รชtre entre `00:00` et `{0}`", - "invalidEndTime": "Heure de fin invalide, doit รชtre entre `00:00` et `{0}`", - "invalidTimeOrder": "L'heure de fin ne peut pas รชtre infรฉrieure ou รฉgale ร  celle de dรฉbut", - "setStageAnnounceTemplate": "C'est fait ! Dรฉsormais, le statut vocal tel que celui dans lequel vous รชtes sera nommรฉ selon votre modรจle. Vous devriez le voir se mettre ร  jour dans quelques secondes.", - "createSongRequestChannel": "Un salon de demande de chansons ({0}) a รฉtรฉ crรฉรฉ ! Vous pouvez commencer ร  demander n'importe quelle chanson par nom ou URL dans ce salon, sans besoin d'utiliser le prรฉfixe du bot." -} \ No newline at end of file + "common": { + "errors": { + "unknown": "โš ๏ธ Une erreur est survenue lors de l'exรฉcution de la commande ! Veuillez rรฉessayer plus tard ou rejoignez notre serveur Discord pour plus d'assistance.", + "missingIntents": "Dรฉsolรฉ, cette commande ne peut pas รชtre exรฉcutรฉe car le bot n'a pas l'intent requis : `({0})`." + }, + "status": { + "enabled": "activรฉ", + "disabled": "dรฉsactivรฉ", + "live": "EN DIRECT" + } + }, + "player": { + "errors": { + "nodeReconnect": "Veuillez rรฉessayer aprรจs la reconnexion du node.", + "noPlaySource": "Impossible de trouver une source ร  lire !", + "noPlayer": "Aucun lecteur n'a รฉtรฉ trouvรฉ sur ce serveur.", + "noTrackPlaying": "Aucune chanson n'est en cours de lecture pour le moment", + "noTrackFound": "Aucune chanson trouvรฉe pour cette requรชte ! Veuillez fournir une URL valide." + }, + "dropdown": { + "trackSelect": "Sรฉlectionnez une chanson vers laquelle passer...", + "filterSelect": "Sรฉlectionnez un filtre ร  appliquer..." + }, + "buttons": { + "back": "Prรฉcรฉdent", + "pause": "Pause", + "resume": "Reprendre", + "skip": "Passer", + "leave": "Quitter", + "loop": "Boucle", + "volumeUp": "Volume +", + "volumeDown": "Volume -", + "volumeMute": "Muet", + "volumeUnmute": "Son activรฉ", + "autoPlay": "Autoplay", + "shuffle": "Mรฉlanger", + "forward": "Avancer", + "rewind": "Rembobiner", + "lyrics": "Paroles" + }, + "playback": { + "nowplayingDesc": "**Lecture en cours :**\n```{0}```", + "nowplayingField": "ร€ suivre :", + "nowplayingLink": "ร‰couter sur {0}", + "playlistLoad": " ๐ŸŽถ La playlist **{0}** avec `{1}` chansons a รฉtรฉ ajoutรฉe ร  la file.", + "trackLoad": "**[{0}](<{1}>)** de **{2}** (`{3}`) a รฉtรฉ ajoutรฉe et commencera ร  jouer.\n", + "trackLoadPos": "**[{0}](<{1}>)** de **{2}** (`{3}`) a รฉtรฉ ajoutรฉe ร  la file ร  la position **{4}**\n" + }, + "controls": { + "pause": { + "error": "Le lecteur est dรฉjร  en pause.", + "vote": "{0} a votรฉ pour mettre la chanson en pause. [{1}/{2}]", + "success": "`{0}` a mis le lecteur en pause." + }, + "resume": { + "error": "Le lecteur n'est pas en pause.", + "vote": "{0} a votรฉ pour reprendre la chanson. [{1}/{2}]", + "success": "`{0}` a repris le lecteur." + }, + "shuffle": { + "error": "Ajoutez plus de chansons ร  la file avant de mรฉlanger.", + "vote": "{0} a votรฉ pour mรฉlanger la file. [{1}/{2}]", + "success": "La file a รฉtรฉ mรฉlangรฉe." + }, + "skip": { + "error": "Aucune chanson vers laquelle passer.", + "vote": "{0} a votรฉ pour passer la chanson. [{1}/{2}]", + "success": "`{0}` a passรฉ la chanson." + }, + "back": { + "vote": "{0} a votรฉ pour passer ร  la chanson prรฉcรฉdente. [{1}/{2}]", + "success": "`{0}` est en train de passer ร  la chanson prรฉcรฉdente." + }, + "leave": { + "vote": "{0} a votรฉ pour arrรชter le lecteur. [{1}/{2}]", + "success": "`{0}` a arrรชtรฉ le lecteur." + }, + "seek": "Le lecteur a รฉtรฉ placรฉ ร  **{0}**", + "repeat": "Le mode de rรฉpรฉtition a รฉtรฉ rรฉglรฉ sur `{0}`", + "forward": "Le lecteur a avancรฉ ร  **{0}**", + "rewind": "Le lecteur a รฉtรฉ rembobinรฉ ร  **{0}**", + "replay": "Relecture de la chanson actuelle.", + "autoplay": "Le mode autoplay est maintenant **{0}**" + } + }, + "voice": { + "connection": { + "noChannel": "Aucun salon vocal pour se connecter. Veuillez en fournir un ou rejoindre un salon vocal.", + "alreadyConnected": "Dรฉjร  connectรฉ ร  un salon vocal.", + "noPermission": "Dรฉsolรฉ ! Je n'ai pas les permissions pour rejoindre ou parler dans votre salon vocal.", + "notInChannel": "{0}, vous devez รชtre dans {1} pour utiliser les commandes vocales. Veuillez rejoindre ร  nouveau si vous รชtes dรฉjร  en vocal !", + "connect": "Connectรฉ ร  {0}" + }, + "stageChannel": { + "setAnnounceTemplate": "C'est fait ! Dรฉsormais, le statut vocal tel que celui dans lequel vous รชtes sera nommรฉ selon votre modรจle. Vous devriez le voir se mettre ร  jour dans quelques secondes." + } + }, + "permissions": { + "noCreatePermission": "Dรฉsolรฉ ! Je n'ai pas les permissions pour crรฉer un salon de demande de chansons.", + "missingPosition": "Seul le DJ ou les admins peuvent changer la position.", + "missingMode": "Seul le DJ ou les admins peuvent changer le mode de boucle.", + "missingQueue": "Seul le DJ ou les admins peuvent retirer un morceau de la file.", + "missingAutoPlay": "Seul le DJ ou les admins peuvent activer/dรฉsactiver l'autoplay !", + "missingFunction": "Seul le DJ ou un admin peut utiliser cette fonction.", + "notDj": "Vous n'รชtes pas DJ. Le DJ actuel est {0}.", + "djToSelf": "Impossible de transfรฉrer le rรดle de DJ ร  vous-mรชme ou ร  un bot.", + "djNotInChannel": "`{0}` n'est pas dans le salon vocal.", + "djSwapped": "Vous avez transfรฉrรฉ le rรดle de DJ ร  `{0}`." + }, + "settings": { + "actions": { + "languageNotFound": "Aucun pack de langue trouvรฉ ! Veuillez sรฉlectionner un pack de langue existant.", + "languageChanged": "Changement rรฉussi vers le pack de langue `{0}`.", + "prefixSet": "C'est fait ! Mon prรฉfixe dans votre serveur est dรฉsormais `{0}`. Essayez de lancer `{1}ping` pour tester.", + "djSet": "Le DJ a รฉtรฉ dรฉfini sur {0}.", + "queueModeSet": "Le mode file d'attente est dรฉfini sur `{0}`.", + "mode247": "Le mode 24/7 est maintenant `{0}`.", + "bypassVote": "Vous avez le systรจme de vote `{0}`.", + "volumeSet": "Le volume est rรฉglรฉ ร  `{0}`%", + "controllerToggled": "Vous avez maintenant `{0}` le contrรดleur de musique.", + "duplicateTrackToggled": "Vous avez maintenant `{0}` la prรฉvention des morceaux en double.", + "controllerMsgToggled": "Vous avez maintenant `{0}` les messages du contrรดleur musical.", + "silentMsgToggled": "Vous avez {0} messages silencieux." + }, + "menu": "Paramรจtres du serveur | {0}", + "basic": { + "title": "โค๏ธ Infos de base :", + "value": "```Prรฉfixe : {0}\nLangue : {1}\nContrรดleur musique : {2}\nRรดle DJ : @{3}\nVote Bypass : {4}\n24/7 : {5}\nVolume par dรฉfaut : {6}%\nTemps de lecture : {7}```" + }, + "queue": { + "title": "๐Ÿ”— Infos sur la file :", + "value": "```Mode de file : {0}\nMaximum de chansons : {1}\nPiste dupliquรฉe : {2}```" + }, + "voice": { + "title": "๐ŸŽค Infos sur l'รฉtat vocal :" + }, + "permissions": { + "title": "โœจ Permissions :", + "value": "```{0} Administrateur\n{1} Gรฉrer_serveur\n{2} Gรฉrer_salons\n{3} Gรฉrer_messages```" + } + }, + "ping": { + "title1": "Infos Bot :", + "title2": "Infos Lecteur :", + "field1": "```Shard ID : {0}/{1}\nLatence Shard : {2:.3f}s {3}\nRรฉgion : {4}```", + "field2": "```Node : {0} - {1:.3f}s\nLecteurs : {2}\nRรฉgion vocale : {3}```" + }, + "effects": { + "added": "Effet `{0}` appliquรฉ.", + "cleared": "Les effets sonores ont รฉtรฉ supprimรฉs !", + "tagInUse": "Cet effet sonore est dรฉjร  utilisรฉ ! Veuillez utiliser /cleareffect pour le retirer." + }, + "playlist": { + "view": { + "title": "Toutes les playlists de {0}", + "headers": "ID:,Durรฉe:,Nom:,Morceaux:", + "footer": "Tapez /playlist play [playlist] pour ajouter une playlist ร  la file.", + "detailTitle": "Visualiseur de playlist", + "detailDesc": "```Nom: {0} [{1}]\nTotal Morceaux : {2}\nPropriรฉtaire : {3}\nType : {4}\n```", + "permsValue": "๐Ÿ“– Lecture : โœ“ โœ๐Ÿฝ ร‰criture : {0} ๐Ÿ—‘๏ธ Suppression : {1}", + "permsValue2": "๐Ÿ“– Lecture : {0}", + "trackList": "Morceaux", + "footer2": "Durรฉe totale : {0}", + "playlistSelect": "Sรฉlectionnez une playlist ร  afficher..." + }, + "errors": { + "notFound": "Playlist [`{0}`] introuvable. Tapez /playlist view pour voir toutes vos playlists.", + "noAccess": "Dรฉsolรฉ ! Vous n'รชtes pas autorisรฉ ร  accรฉder ร  cette playlist !", + "noTrack": "Dรฉsolรฉ ! Il n'y a aucun morceau dans la playlist [`{0}`].", + "notAllowed": "Cette commande n'est pas autorisรฉe sur les playlists liรฉes ou partagรฉes.", + "nameOverLimit": "Dรฉsolรฉ ! Le nom de la playlist ne peut pas dรฉpasser 10 caractรจres.", + "sameName": "Dรฉsolรฉ ! Veuillez choisir un nouveau nom pour la playlist.", + "deleteDefault": "Vous n'รชtes pas autorisรฉ ร  supprimer la playlist par dรฉfaut.", + "limitReached": "Vous ne pouvez pas crรฉer plus de `{0}` playlists !", + "exists": "La playlist [`{0}`] existe dรฉjร .", + "invalidUrl": "Veuillez entrer un lien valide de playlist publique Spotify ou YouTube.", + "trackLimitReached": "Vous avez atteint la limite ! Vous ne pouvez ajouter que `{0}` chansons ร  votre playlist.", + "playlistLinkNotAllowed": "Vous n'รชtes pas autorisรฉ ร  utiliser des liens de playlist.", + "streamNotAllowed": "Vous n'รชtes pas autorisรฉ ร  ajouter des vidรฉos en streaming ร  votre playlist.", + "positionNotFound": "Impossible de trouver la position `{0}` dans votre playlist [`{1}`] !", + "streaming": "Vous n'รชtes pas autorisรฉ ร  ajouter des vidรฉos en streaming ร  votre playlist !", + "track": "Problรจme lors de l'ajout de morceaux ร  la playlist !", + "trackLimited": "Vous avez atteint la limite ! Vous ne pouvez ajouter que {0} chansons ร  votre playlist.", + "trackRepeated": "Ce morceau est dรฉjร  dans votre playlist !" + }, + "actions": { + "play": "La playlist [`{0}`] avec `{1}` chansons a รฉtรฉ ajoutรฉe ร  la file.", + "removed": "Vous avez supprimรฉ la playlist [`{0}`].", + "created": "Vous avez crรฉรฉ `{0}` playlists. Tapez /playlist view pour plus d'infos.", + "renamed": "Vous avez renommรฉ `{0}` en `{1}`.", + "trackRemoved": "๐Ÿ‘‹ **{0}** a รฉtรฉ retirรฉ de la playlist [`{2}`] de {1}.", + "cleared": "Vous avez vidรฉ avec succรจs votre playlist [`{0}`].", + "trackAdded": "โค๏ธ **{0}** ajoutรฉ ร  la playlist [`{2}`] de {1} !" + }, + "sharing": { + "sendErrorPlayer": "Dรฉsolรฉ ! Vous ne pouvez pas vous envoyer une invitation ร  vous-mรชme.", + "sendErrorBot": "Dรฉsolรฉ ! Vous ne pouvez pas envoyer une invitation ร  un bot.", + "belongs": "Dรฉsolรฉ ! Cette playlist appartient ร  <@{0}>.", + "alreadyShared": "Dรฉsolรฉ ! Cette playlist a รฉtรฉ partagรฉe avec {0}.", + "alreadySent": "Dรฉsolรฉ ! Vous avez dรฉjร  envoyรฉ une invitation ร  cet utilisateur auparavant.", + "noAccount": "{0} n'a pas crรฉรฉ de compte playlist.", + "invitationSent": "Invitation envoyรฉe ร  {0}." + }, + "inbox": { + "full": "Dรฉsolรฉ ! La boรฎte de rรฉception de {0} est pleine.", + "noMessages": "Aucun message dans votre boรฎte de rรฉception." + } + }, + "search": { + "noLinkSupport": "La commande de recherche ne prend pas en charge les liens !", + "title": "Recherche : {0}", + "desc": "โžฅ Plateforme : {0} **{1}**\nโžฅ Rรฉsultats : **{2}**\n\n{3}", + "wait": "Choisissez la chanson ร  ajouter ร  la file.", + "success": "Chanson ajoutรฉe ร  la file." + }, + "voting": { + "voted": "Vous avez votรฉ !" + }, + "time": { + "formatError": "Format de temps incorrect. Exemple : 2:42 ou 12:39:31", + "invalidStartTime": "Heure de dรฉbut invalide, doit รชtre entre `00:00` et `{0}`", + "invalidEndTime": "Heure de fin invalide, doit รชtre entre `00:00` et `{0}`" + }, + "lyrics": { + "notFound": "Paroles non trouvรฉes. Tapez /lyrics pour chercher les paroles." + }, + "queue": { + "management": { + "title": "File ร  venir :", + "cleared": "Tous les morceaux dans `{0}` ont รฉtรฉ supprimรฉs", + "removed": "`{0}` morceaux ont รฉtรฉ retirรฉs de la file.", + "swapped": "`{0}` et `{1}` ont รฉtรฉ รฉchangรฉs", + "moved": "`{0}` a รฉtรฉ dรฉplacรฉ vers `{1}`" + }, + "view": { + "historyTitle": "Historique :", + "title": "File de musiques", + "desc": "**Lecture en cours : [Cliquez ici]({0}) โฎฏ**\n{1}" + }, + "errors": { + "queueFull": "Dรฉsolรฉ, vous avez atteint le maximum de `{0}` morceaux dans la file !", + "outOfList": "Veuillez fournir un index de morceau valide !", + "duplicateTrack": "Dรฉsolรฉ, ce morceau est dรฉjร  dans la file." + } + }, + "songRequest": { + "channelCreated": "Un salon de demande de chansons ({0}) a รฉtรฉ crรฉรฉ ! Vous pouvez commencer ร  demander n'importe quelle chanson par nom ou URL dans ce salon, sans besoin d'utiliser le prรฉfixe du bot." + }, + "pagination": { + "prev": "Prรฉcรฉdent", + "next": "Suivant", + "play": "Jouer", + "share": "Partager", + "export": "Exporter", + "delete": "Supprimer", + "page": { + "title": "Entrez le numรฉro de page", + "field": "Numรฉro de page" + } + } +} diff --git a/langs/JA.json b/langs/JA.json index 88660fe..56133a1 100644 --- a/langs/JA.json +++ b/langs/JA.json @@ -1,172 +1,262 @@ { - "unknownException": "โš ๏ธ ใ‚ณใƒžใƒณใƒ‰ใฎๅฎŸ่กŒไธญใซไฝ•ใ‹ใŒ้–“้•ใฃใฆใ„ใพใ™๏ผๅพŒใงใ‚‚ใ†ไธ€ๅบฆ่ฉฆใ—ใฆใใ ใ•ใ„ใ€‚ใพใŸใฏใ€ใ‚ˆใ‚Š่ฉณใ—ใ„ใ‚ตใƒใƒผใƒˆใ‚’ๆฑ‚ใ‚ใ‚‹ใŸใ‚ใซๅฝ“็คพใฎDiscordใ‚ตใƒผใƒใƒผใซๅ‚ๅŠ ใ—ใฆใใ ใ•ใ„ใ€‚", - "enabled": "ๆœ‰ๅŠนๅŒ–", - "disabled": "็„กๅŠนๅŒ–", - "nodeReconnect": "ๅ†่ฉฆ่กŒใ—ใฆใใ ใ•ใ„๏ผใƒŽใƒผใƒ‰ใŒๅ†ๆŽฅ็ถšใ—ใŸๅพŒใ€‚", - "noChannel": "ๆŽฅ็ถšใ™ใ‚‹้ŸณๅฃฐใƒใƒฃใƒณใƒใƒซใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚ๆไพ›ใ™ใ‚‹ใ‹ใ€ๅ‚ๅŠ ใ—ใฆใใ ใ•ใ„ใ€‚", - "alreadyConnected": "ใ™ใงใซ้ŸณๅฃฐใƒใƒฃใƒณใƒใƒซใซๆŽฅ็ถšใ—ใฆใ„ใพใ™ใ€‚", - "noPermission": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผ็งใฏใ‚ใชใŸใฎ้Ÿณๅฃฐใƒใƒฃใƒณใƒใƒซใซๅ‚ๅŠ ใพใŸใฏ่ฉฑใ™ใŸใ‚ใฎ่จฑๅฏใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚", - "noCreatePermission": "ใ”ใ‚ใ‚“ใชใ•ใ„๏ผๆ›ฒใƒชใ‚ฏใ‚จใ‚นใƒˆใƒใƒฃใƒณใƒใƒซใ‚’ไฝœๆˆใ™ใ‚‹ๆจฉ้™ใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚", - "noPlaySource": "ๅ†็”Ÿๅฏ่ƒฝใชใ‚ฝใƒผใ‚นใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“๏ผ", - "noPlayer": "ใ“ใฎใ‚ตใƒผใƒใƒผใซใƒ—ใƒฌใ‚คใƒคใƒผใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใ€‚", - "notVote": "ใ“ใฎใ‚ณใƒžใƒณใƒ‰ใซใฏใ‚ใชใŸใฎๆŠ•็ฅจใŒๅฟ…่ฆใงใ™๏ผ่ฉณ็ดฐใซใคใ„ใฆใฏใ€/voteใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚", - "missingIntents": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“ใŒใ€ใ“ใฎใ‚ณใƒžใƒณใƒ‰ใฏๅฎŸ่กŒใงใใพใ›ใ‚“ใ€‚ใƒœใƒƒใƒˆใซๅฟ…่ฆใชใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚คใƒณใƒ†ใƒณใƒˆใŒไธ่ถณใ—ใฆใ„ใพใ™๏ผš`({0})`.", - "languageNotFound": "่จ€่ชžใƒ‘ใƒƒใ‚ฏใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใ€‚ๆ—ขๅญ˜ใฎ่จ€่ชžใƒ‘ใƒƒใ‚ฏใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", - "changedLanguage": "ใ€Œ{0}ใ€่จ€่ชžใƒ‘ใƒƒใ‚ฏใซๆญฃๅธธใซๅค‰ๆ›ดใ—ใพใ—ใŸใ€‚", - "setPrefix": "ๅฎŒไบ†๏ผใ‚ใชใŸใฎใ‚ตใƒผใƒใƒผใฎใƒ—ใƒฌใƒ•ใ‚ฃใƒƒใ‚ฏใ‚นใฏไปŠใ‚„ใ€Œ{0}ใ€ใงใ™ใ€‚ `{1}ping`ใ‚’ๅฎŸ่กŒใ—ใฆใƒ†ใ‚นใƒˆใ—ใฆใฟใฆใใ ใ•ใ„ใ€‚", - "setDJ": "{0}ใซDJใ‚’่จญๅฎšใ—ใพใ—ใŸใ€‚", - "setQueue": "ใ‚ญใƒฅใƒผใƒขใƒผใƒ‰ใ‚’ใ€Œ{0}ใ€ใซ่จญๅฎšใ—ใพใ—ใŸใ€‚", - "247": "ไปŠใ€24/7ใƒขใƒผใƒ‰ใฏใ€Œ{0}ใ€ใงใ™ใ€‚", - "bypassVote": "ไปŠใ€ๆŠ•็ฅจใ‚ทใ‚นใƒ†ใƒ ใฏใ€Œ{0}ใ€ใซใชใ‚Šใพใ—ใŸใ€‚", - "setVolume": "้Ÿณ้‡ใ‚’ใ€Œ{0}๏ผ…ใ€ใซ่จญๅฎšใ—ใพใ—ใŸใ€‚", - "toggleController": "ไปŠใ€้Ÿณๆฅฝใ‚ณใƒณใƒˆใƒญใƒผใƒฉใƒผใฏใ€Œ{0}ใ€ใงใ™ใ€‚", - "toggleDuplicateTrack": "ไปŠใ€ใ‚ญใƒฅใƒผๅ†…ใฎ้‡่ค‡ใƒˆใƒฉใƒƒใ‚ฏใ‚’้˜ฒๆญขใ™ใ‚‹ใŸใ‚ใฎ่จญๅฎšใฏใ€Œ{0}ใ€ใงใ™ใ€‚", - "toggleControllerMsg": "็พๅœจใ€้Ÿณๆฅฝใ‚ณใƒณใƒˆใƒญใƒผใƒฉใƒผใ‹ใ‚‰ `{0}` ไปถใฎใƒกใƒƒใ‚ปใƒผใ‚ธใŒใ‚ใ‚Šใพใ™ใ€‚", - "toggleSilentMsg": "ใ‚ใชใŸใฏ{0}ใ‚ตใ‚คใƒฌใƒณใƒˆใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ๆŒใฃใฆใ„ใพใ™ใ€‚", - "settingsMenu": "ใ‚ตใƒผใƒใƒผ่จญๅฎš| {0}", - "settingsTitle": "โค๏ธ ๅŸบๆœฌๆƒ…ๅ ฑ๏ผš", - "settingsValue": "```ใƒ—ใƒฌใƒ•ใ‚ฃใƒƒใ‚ฏใ‚น๏ผš{0}\n่จ€่ชž๏ผš{1}\n้Ÿณๆฅฝใ‚ณใƒณใƒˆใƒญใƒผใƒฉใƒผใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹๏ผš{2}\nDJใƒญใƒผใƒซ๏ผš@{3}\nๆŠ•็ฅจใƒใ‚คใƒ‘ใ‚น๏ผš{4}\n24/7๏ผš{5}\nใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใƒœใƒชใƒฅใƒผใƒ ๏ผš{6}๏ผ…\nๅ†็”Ÿๆ™‚้–“๏ผš{7}```", - "settingsTitle2": "๐Ÿ”— ใ‚ญใƒฅใƒผๆƒ…ๅ ฑ๏ผš", - "settingsValue2": "```ใ‚ญใƒฅใƒผใƒขใƒผใƒ‰๏ผš{0}\nๆœ€ๅคงๆ›ฒๆ•ฐ๏ผš{1}\n้‡่ค‡ใƒˆใƒฉใƒƒใ‚ฏใ‚’่จฑๅฏใ™ใ‚‹๏ผš{2}```", - "settingsTitle3": "๐ŸŽค ้Ÿณๅฃฐใ‚นใƒ†ใƒผใ‚ฟใ‚นๆƒ…ๅ ฑ๏ผš", - "settingsPermTitle": "โœจ ๆจฉ้™๏ผš", - "settingsPermValue": "```{0} ็ฎก็†่€…\n{1} ใ‚ฎใƒซใƒ‰็ฎก็†\n{2} ใƒใƒฃใƒณใƒใƒซ็ฎก็†\n{3} ใƒกใƒƒใ‚ปใƒผใ‚ธ็ฎก็†```", - "pingTitle1": "ใƒœใƒƒใƒˆๆƒ…ๅ ฑ๏ผš", - "pingTitle2": "ใƒ—ใƒฌใƒผใƒคใƒผๆƒ…ๅ ฑ๏ผš", - "pingField1": "```ใ‚ทใƒฃใƒผใƒ‰ID๏ผš{0}/{1}\nใ‚ทใƒฃใƒผใƒ‰ใƒฌใ‚คใƒ†ใƒณใ‚ท๏ผš{2:.3f}s {3}\nใƒชใƒผใ‚ธใƒงใƒณ๏ผš{4}```", - "pingField2": "```ใƒŽใƒผใƒ‰๏ผš{0} - {1:.3f}s\nใƒ—ใƒฌใ‚คใƒคใƒผๆ•ฐ๏ผš{2}\n้Ÿณๅฃฐใƒชใƒผใ‚ธใƒงใƒณ๏ผš{3}```", - "addEffect": "`{0}` ใƒ•ใ‚ฃใƒซใ‚ฟใƒผใ‚’้ฉ็”จใ—ใพใ™ใ€‚", - "clearEffect": "ๅŠนๆžœ้ŸณใŒใ‚ฏใƒชใ‚ขใ•ใ‚Œใพใ—ใŸ๏ผ", - "filterTagAlreadyInUse": "ใ“ใฎใ‚ตใ‚ฆใƒณใƒ‰ใ‚จใƒ•ใ‚งใ‚ฏใƒˆใฏใ™ใงใซไฝฟ็”จใ•ใ‚Œใฆใ„ใพใ™๏ผๅ‰Š้™คใ™ใ‚‹ใซใฏ/cleareffect ใ‚’ไฝฟ็”จใ—ใฆใใ ใ•ใ„ใ€‚", - "playlistViewTitle": "๐Ÿ“œ {0}ใฎใ™ในใฆใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ", - "playlistViewHeaders": "ID:,ๆ™‚้–“:,ๅๅ‰:,ใƒˆใƒฉใƒƒใ‚ฏ:", - "playlistFooter": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใ‚’ใ‚ญใƒฅใƒผใซ่ฟฝๅŠ ใ™ใ‚‹ใซใฏใ€/playlist play [playlist]ใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚", - "playlistNotFound": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[{0}]ใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใ€‚ใ™ในใฆใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใ‚’่กจ็คบใ™ใ‚‹ใซใฏใ€/playlist viewใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚", - "playlistNotAccess": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผใ“ใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใซใ‚ขใ‚ฏใ‚ปใ‚นใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“๏ผ", - "playlistNoTrack": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[{0}]ใซใฏใƒˆใƒฉใƒƒใ‚ฏใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚", - "playlistNotAllow": "ใƒชใƒณใ‚ฏใพใŸใฏๅ…ฑๆœ‰ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใงใฏใ€ใ“ใฎใ‚ณใƒžใƒณใƒ‰ใฏ่จฑๅฏใ•ใ‚Œใฆใ„ใพใ›ใ‚“ใ€‚", - "playlistPlay": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[{0}]ใ‚’ใ‚ญใƒฅใƒผใซ{1}ๆ›ฒ่ฟฝๅŠ ใ—ใพใ—ใŸใ€‚", - "playlistOverText": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใฎๅๅ‰ใฏ10ๆ–‡ๅญ—ใ‚’่ถ…ใˆใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“ใ€‚", - "playlistSameName": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผใ“ใฎๅๅ‰ใฏๆ–ฐใ—ใ„ๅๅ‰ใจๅŒใ˜ใงใ‚ใฃใฆใฏใชใ‚Šใพใ›ใ‚“ใ€‚", - "playlistDeleteError": "ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใ‚’ๅ‰Š้™คใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“ใ€‚", - "playlistRemove": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[{0}]ใ‚’ๅ‰Š้™คใ—ใพใ—ใŸใ€‚", - "playlistSendErrorPlayer": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผ่‡ชๅˆ†ใซๆ‹›ๅพ…็Šถใ‚’้€ไฟกใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“ใ€‚", - "playlistSendErrorBot": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผใƒœใƒƒใƒˆใซๆ‹›ๅพ…็Šถใ‚’้€ไฟกใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“ใ€‚", - "playlistBelongs": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผใ“ใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใฏ<@{0}>ใ•ใ‚“ใฎใ‚‚ใฎใงใ™ใ€‚", - "playlistShare": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผใ“ใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใฏ{0}ใ•ใ‚“ใจๅ…ฑๆœ‰ใ•ใ‚Œใฆใ„ใพใ™ใ€‚", - "playlistSent": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผไปฅๅ‰ใซๆ‹›ๅพ…็Šถใ‚’้€ไฟกใ—ใพใ—ใŸใ€‚", - "noPlaylistAcc": "{0}ใ•ใ‚“ใฏใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใ‚ขใ‚ซใ‚ฆใƒณใƒˆใ‚’ไฝœๆˆใ—ใฆใ„ใพใ›ใ‚“ใ€‚", - "overPlaylistCreation": " {0}ๅ€‹ไปฅไธŠใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใ‚’ไฝœๆˆใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“๏ผ", - "playlistExists": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[{0}]ใฏใ™ใงใซๅญ˜ๅœจใ—ใพใ™ใ€‚", - "playlistNotInvalidUrl": "ๆœ‰ๅŠนใชใƒชใƒณใ‚ฏใพใŸใฏๅ…ฌ้–‹SpotifyใพใŸใฏYouTubeใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใƒชใƒณใ‚ฏใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚", - "playlistCreated": "{0}ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใ‚’ไฝœๆˆใ—ใพใ—ใŸใ€‚่ฉณ็ดฐใซใคใ„ใฆใฏใ€/playlist viewใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚", - "playlistRenamed": "{0}ใ‚’{1}ใซๅๅ‰ใ‚’ๅค‰ๆ›ดใ—ใพใ—ใŸใ€‚", - "playlistLimitTrack": "ใ“ใฎๅˆถ้™ใซ้”ใ—ใพใ—ใŸ๏ผใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใซใฏ{0}ๆ›ฒใ—ใ‹่ฟฝๅŠ ใงใใพใ›ใ‚“ใ€‚", - "playlistPlaylistLink": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใƒชใƒณใ‚ฏใ‚’ไฝฟ็”จใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“ใ€‚", - "playlistStream": "ใ‚นใƒˆใƒชใƒผใƒŸใƒณใ‚ฐใƒ“ใƒ‡ใ‚ชใ‚’ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใซ่ฟฝๅŠ ใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“ใ€‚", - "playlistPositionNotFound": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[{1}]ใ‹ใ‚‰ไฝ็ฝฎ{0}ใ‚’่ฆ‹ใคใ‘ใ‚‹ใ“ใจใŒใงใใพใ›ใ‚“ใงใ—ใŸ๏ผ", - "playlistRemoved": "๐Ÿ‘‹ {1}ใ•ใ‚“ใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[{2}]ใ‹ใ‚‰**{0}**ใ‚’ๅ‰Š้™คใ—ใพใ—ใŸใ€‚", - "playlistClear": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[{0}]ใ‚’ๆญฃๅธธใซใ‚ฏใƒชใ‚ขใ—ใพใ—ใŸใ€‚", - "playlistView": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใƒ“ใƒฅใƒผใ‚ขใƒผ", - "playlistViewDesc": "```ๅๅ‰ | ID: {0} | {1}\nใƒˆใƒผใ‚ฟใƒซใƒˆใƒฉใƒƒใ‚ฏ: {2}\nใ‚ชใƒผใƒŠใƒผ: {3}\nใ‚ฟใ‚คใƒ—: {4}\n```", - "playlistViewPermsValue": "๐Ÿ“– ่ชญใฟ่พผใฟ: โœ“ โœ๐Ÿฝ ๆ›ธใ่พผใฟ: {0} ๐Ÿ—‘๏ธ ๅ‰Š้™ค: {1}", - "playlistViewPermsValue2": "๐Ÿ“– ่ชญใฟ่พผใฟ: {0}", - "playlistViewTrack": "ใƒˆใƒฉใƒƒใ‚ฏ", - "playlistViewPage": "ใƒšใƒผใ‚ธ: {0}/{1} | ใƒˆใƒผใ‚ฟใƒซใƒ€ใƒฌใƒผใ‚ทใƒงใƒณ: {2}", - "inboxFull": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผ{0}ใ•ใ‚“ใฎๅ—ไฟกใƒˆใƒฌใ‚คใฏใ„ใฃใฑใ„ใงใ™ใ€‚", - "inboxNoMsg": "ๅ—ไฟกใƒˆใƒฌใ‚คใซใƒกใƒƒใ‚ปใƒผใ‚ธใฏใ‚ใ‚Šใพใ›ใ‚“ใ€‚", - "invitationSent": "{0}ใ•ใ‚“ใซๆ‹›ๅพ…็Šถใ‚’้€ไฟกใ—ใพใ—ใŸใ€‚", - "notInChannel": "{0}ใ•ใ‚“ใ€้Ÿณๅฃฐใ‚ณใƒžใƒณใƒ‰ใ‚’ไฝฟ็”จใ™ใ‚‹ใซใฏ{1}ใซใ„ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚้ŸณๅฃฐใซๆŽฅ็ถšใ—ใฆใ„ใ‚‹ๅ ดๅˆใฏๅ†ๆŽฅ็ถšใ—ใฆใใ ใ•ใ„๏ผ", - "noTrackPlaying": "็พๅœจๅ†็”Ÿไธญใฎๆ›ฒใฏใ‚ใ‚Šใพใ›ใ‚“", - "noTrackFound": "ใใฎใ‚ฏใ‚จใƒชใงๆ›ฒใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใงใ—ใŸ๏ผๆœ‰ๅŠนใชURLใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚", - "noLinkSupport": "ๆคœ็ดขใ‚ณใƒžใƒณใƒ‰ใฏใƒชใƒณใ‚ฏใ‚’ใ‚ตใƒใƒผใƒˆใ—ใฆใ„ใพใ›ใ‚“๏ผ", - "voted": "ๆŠ•็ฅจใ—ใพใ—ใŸ๏ผ", - "missingPosPerm": "DJใพใŸใฏ็ฎก็†่€…ใฎใฟใŒไฝ็ฝฎใ‚’ๅค‰ๆ›ดใงใใพใ™ใ€‚", - "missingModePerm": "DJใพใŸใฏ็ฎก็†่€…ใฎใฟใŒใƒซใƒผใƒ—ใƒขใƒผใƒ‰ใ‚’ๅˆ‡ใ‚Šๆ›ฟใˆใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚", - "missingQueuePerm": "DJใพใŸใฏ็ฎก็†่€…ใฎใฟใŒใ‚ญใƒฅใƒผใ‹ใ‚‰ใƒˆใƒฉใƒƒใ‚ฏใ‚’ๅ‰Š้™คใงใใพใ™ใ€‚", - "missingAutoPlayPerm": "DJใพใŸใฏ็ฎก็†่€…ใฎใฟใŒใ‚ชใƒผใƒˆใƒ—ใƒฌใ‚คใƒขใƒผใƒ‰ใ‚’ๆœ‰ๅŠนๅŒ–ใพใŸใฏ็„กๅŠนๅŒ–ใงใใพใ™๏ผ", - "missingFunctionPerm": "DJใพใŸใฏ็ฎก็†่€…ใฎใฟใŒใ“ใฎๆฉŸ่ƒฝใ‚’ไฝฟ็”จใงใใพใ™ใ€‚", - "timeFormatError": "ๆ™‚้–“ใฎๅฝขๅผใŒ้–“้•ใฃใฆใ„ใพใ™ใ€‚ไพ‹๏ผš2:42ใพใŸใฏ12:39:31", - "lyricsNotFound": "ๆญŒ่ฉžใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใงใ—ใŸใ€‚/lyrics <ๆ›ฒๅ> <ใ‚ขใƒผใƒ†ใ‚ฃใ‚นใƒˆ>ใจๅ…ฅๅŠ›ใ—ใฆๆญŒ่ฉžใ‚’ๆคœ็ดขใ—ใฆใใ ใ•ใ„ใ€‚", - "missingTrackInfo": "ไธ€้ƒจใฎใƒˆใƒฉใƒƒใ‚ฏๆƒ…ๅ ฑใŒๆฌ ่ฝใ—ใฆใ„ใพใ™ใ€‚", - "noVoiceChannel": "้ŸณๅฃฐใƒใƒฃใƒณใƒใƒซใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“๏ผ", - "playlistAddError": "ใ‚นใƒˆใƒชใƒผใƒŸใƒณใ‚ฐใƒ“ใƒ‡ใ‚ชใ‚’ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใซ่ฟฝๅŠ ใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“๏ผ", - "playlistAddError2": "ใƒˆใƒฉใƒƒใ‚ฏใ‚’ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใซ่ฟฝๅŠ ใ™ใ‚‹้š›ใซๅ•้กŒใŒ็™บ็”Ÿใ—ใพใ—ใŸ๏ผ", - "playlistLimited": "ๅˆถ้™ใซ้”ใ—ใพใ—ใŸ๏ผใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใซใฏ{0}ๆ›ฒใ—ใ‹่ฟฝๅŠ ใงใใพใ›ใ‚“ใ€‚", - "playlistRepeated": "ใ™ใงใซใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใซๅŒใ˜ใƒˆใƒฉใƒƒใ‚ฏใŒใ‚ใ‚Šใพใ™๏ผ", - "playlistAdded": "โค๏ธ **{0}**ใ‚’{1}ใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[`{2}`]ใซ่ฟฝๅŠ ใ—ใพใ—ใŸ๏ผ", - "playerDropdown": "ใ‚นใ‚ญใƒƒใƒ—ใ™ใ‚‹ๆ›ฒใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„...", - "playerFilter": "้ฉ็”จใ™ใ‚‹ใƒ•ใ‚ฃใƒซใ‚ฟใƒผใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„...", - "buttonBack": "ๆˆปใ‚‹", - "buttonPause": "ไธ€ๆ™‚ๅœๆญข", - "buttonResume": "ๅ†้–‹", - "buttonSkip": "ใ‚นใ‚ญใƒƒใƒ—", - "buttonLeave": "้€€ๅ‡บ", - "buttonLoop": "ใƒซใƒผใƒ—", - "buttonVolumeUp": "้Ÿณ้‡ใ‚’ไธŠใ’ใ‚‹", - "buttonVolumeDown": "้Ÿณ้‡ใ‚’ไธ‹ใ’ใ‚‹", - "buttonVolumeMute": "ๆถˆ้Ÿณ", - "buttonVolumeUnmute": "ๆถˆ้Ÿณใ‚’่งฃ้™ค", - "buttonAutoPlay": "่‡ชๅ‹•ๅ†็”Ÿ", - "buttonShuffle": "ใ‚ทใƒฃใƒƒใƒ•ใƒซ", - "buttonForward": "้€ฒใ‚€", - "buttonRewind": "ๆˆปใ‚‹", - "buttonLyrics": "ๆญŒ่ฉž", - "nowplayingDesc": "**็พๅœจๅ†็”Ÿไธญ๏ผš**\n```{0}```", - "nowplayingField": "ๆฌกใซๅ†็”Ÿใ™ใ‚‹ๆ›ฒ๏ผš", - "nowplayingLink": "{0}ใง่ดใ", - "connect": "{0}ใซๆŽฅ็ถšใ—ใพใ—ใŸ", - "live": "ใƒฉใ‚คใƒ–", - "playlistLoad": " ๐ŸŽถ ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ**{0}**ใ‚’ใ‚ญใƒฅใƒผใซ`{1}`ๆ›ฒ่ฟฝๅŠ ใ—ใพใ—ใŸใ€‚", - "trackLoad": "**{2}**ใฎ**[{0}](<{1}>)** (`{3}`)ใ‚’ๅ†็”Ÿใ‚’้–‹ๅง‹ใ™ใ‚‹ใŸใ‚ใซ่ฟฝๅŠ ใ—ใพใ—ใŸใ€‚\n", - "trackLoad_pos": "**{2}**ใฎ**[{0}](<{1}>)** (`{3}`)ใ‚’ใ‚ญใƒฅใƒผใฎไฝ็ฝฎ**{4}**ใซ่ฟฝๅŠ ใ—ใพใ—ใŸใ€‚\n", - "searchTitle": "ๆคœ็ดขใ‚ฏใ‚จใƒช๏ผš{0}", - "searchDesc": "โžฅ ใƒ—ใƒฉใƒƒใƒˆใƒ•ใ‚ฉใƒผใƒ ๏ผš{0} **{1}**\nโžฅ ็ตๆžœ๏ผš**{2}**\n\n{3}", - "searchWait": "ใ‚ญใƒฅใƒผใซ่ฟฝๅŠ ใ—ใŸใ„ๆ›ฒใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", - "searchTimeout": "ๆคœ็ดขใŒใ‚ฟใ‚คใƒ ใ‚ขใ‚ฆใƒˆใ—ใพใ—ใŸใ€‚ๅพŒใงใ‚‚ใ†ไธ€ๅบฆใŠ่ฉฆใ—ใใ ใ•ใ„ใ€‚", - "searchSuccess": "ๆ›ฒใŒใ‚ญใƒฅใƒผใซ่ฟฝๅŠ ใ•ใ‚Œใพใ—ใŸใ€‚", - "queueTitle": "ใ‚ญใƒฅใƒผใซ่ฟฝๅŠ ใ•ใ‚ŒใŸๆ›ฒ๏ผš", - "historyTitle": "ๅ†็”Ÿๅฑฅๆญด๏ผš", - "viewTitle": "้Ÿณๆฅฝใ‚ญใƒฅใƒผ", - "viewDesc": "**็พๅœจๅ†็”Ÿไธญ๏ผš[ใ“ใ“ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆ่ดใ]({0}) โฎฏ**\n{1}", - "viewFooter": "ใƒšใƒผใ‚ธ๏ผš{0}/{1} | ๅˆ่จˆๅ†็”Ÿๆ™‚้–“๏ผš{2}", - "pauseError": "ใƒ—ใƒฌใ‚คใƒคใƒผใฏใ™ใงใซไธ€ๆ™‚ๅœๆญขใ—ใฆใ„ใพใ™ใ€‚", - "pauseVote": "{0}ใŒๆ›ฒใ‚’ไธ€ๆ™‚ๅœๆญขใ™ใ‚‹ใ“ใจใซ่ณ›ๆˆใ—ใพใ—ใŸใ€‚[{1}/{2}]", - "paused": "{0}ใŒใƒ—ใƒฌใ‚คใƒคใƒผใ‚’ไธ€ๆ™‚ๅœๆญขใ—ใพใ—ใŸใ€‚", - "resumeError": "ใƒ—ใƒฌใ‚คใƒคใƒผใฏไธ€ๆ™‚ๅœๆญขใ—ใฆใ„ใพใ›ใ‚“ใ€‚", - "resumeVote": "{0}ใŒๆ›ฒใ‚’ๅ†้–‹ใ™ใ‚‹ใ“ใจใซ่ณ›ๆˆใ—ใพใ—ใŸใ€‚[{1}/{2}]", - "resumed": "{0}ใŒใƒ—ใƒฌใ‚คใƒคใƒผใ‚’ๅ†้–‹ใ—ใพใ—ใŸใ€‚", - "shuffleError": "ใ‚ทใƒฃใƒƒใƒ•ใƒซใ™ใ‚‹ๅ‰ใซใ‚ญใƒฅใƒผใซๆ›ฒใ‚’่ฟฝๅŠ ใ—ใฆใใ ใ•ใ„ใ€‚", - "shuffleVote": "{0}ใŒใ‚ญใƒฅใƒผใ‚’ใ‚ทใƒฃใƒƒใƒ•ใƒซใ™ใ‚‹ใ“ใจใซ่ณ›ๆˆใ—ใพใ—ใŸใ€‚[{1}/{2}]", - "shuffled": "ใ‚ญใƒฅใƒผใŒใ‚ทใƒฃใƒƒใƒ•ใƒซใ•ใ‚Œใพใ—ใŸใ€‚", - "skipError": "ใ‚นใ‚ญใƒƒใƒ—ใ™ใ‚‹ๆ›ฒใฏใ‚ใ‚Šใพใ›ใ‚“ใ€‚", - "skipVote": "{0}ใŒๆ›ฒใ‚’ใ‚นใ‚ญใƒƒใƒ—ใ™ใ‚‹ใ“ใจใซ่ณ›ๆˆใ—ใพใ—ใŸใ€‚[{1}/{2}]", - "skipped": "{0}ใŒๆ›ฒใ‚’ใ‚นใ‚ญใƒƒใƒ—ใ—ใพใ—ใŸใ€‚", - "backVote": "{0}ใŒๅ‰ใฎๆ›ฒใซใ‚นใ‚ญใƒƒใƒ—ใ™ใ‚‹ใ“ใจใซ่ณ›ๆˆใ—ใพใ—ใŸใ€‚[{1}/{2}]", - "backed": "`{0}`ใŒๅ‰ใฎๆ›ฒใซใ‚นใ‚ญใƒƒใƒ—ใ—ใพใ—ใŸใ€‚", - "leaveVote": "{0}ใŒใƒ—ใƒฌใ‚คใƒคใƒผใ‚’ๅœๆญขใ™ใ‚‹ใ“ใจใซ่ณ›ๆˆใ—ใพใ—ใŸใ€‚[{1}/{2}]", - "left": "`{0}`ใŒใƒ—ใƒฌใ‚คใƒคใƒผใ‚’ๅœๆญขใ—ใพใ—ใŸใ€‚", - "seek": "ใƒ—ใƒฌใ‚คใƒคใƒผใ‚’ **{0}** ใซ่จญๅฎšใ—ใพใ—ใŸใ€‚", - "repeat": "ใƒชใƒ”ใƒผใƒˆใƒขใƒผใƒ‰ใŒ `{0}` ใซ่จญๅฎšใ•ใ‚Œใพใ—ใŸใ€‚", - "cleared": "`{0}`ใฎใ™ในใฆใฎใƒˆใƒฉใƒƒใ‚ฏใ‚’ใ‚ฏใƒชใ‚ขใ—ใพใ—ใŸใ€‚", - "removed": "`{0}`ใฎใƒˆใƒฉใƒƒใ‚ฏใŒใ‚ญใƒฅใƒผใ‹ใ‚‰ๅ‰Š้™คใ•ใ‚Œใพใ—ใŸใ€‚", - "forward": "ใƒ—ใƒฌใ‚คใƒคใƒผใ‚’ **{0}** ใซ้€ฒใ‚ใพใ—ใŸใ€‚", - "rewind": "ใƒ—ใƒฌใ‚คใƒคใƒผใ‚’ **{0}** ใซๆˆปใ—ใพใ—ใŸใ€‚", - "replay": "็พๅœจใฎๆ›ฒใ‚’ใƒชใƒ—ใƒฌใ‚คใ—ใพใ™ใ€‚", - "swapped": "`{0}`ใจ`{1}`ใŒไบคๆ›ใ•ใ‚Œใพใ—ใŸใ€‚", - "moved": "`{0}`ใ‚’`{1}`ใซ็งปๅ‹•ใ—ใพใ—ใŸใ€‚", - "autoplay": "ใ‚ชใƒผใƒˆใƒ—ใƒฌใ‚คใƒขใƒผใƒ‰ใŒ **{0}** ใซ่จญๅฎšใ•ใ‚Œใพใ—ใŸใ€‚", - "notdj": "DJใงใฏใ‚ใ‚Šใพใ›ใ‚“ใ€‚็พๅœจใฎDJใฏ{0}ใงใ™ใ€‚", - "djToMe": "่‡ชๅˆ†่‡ช่บซใพใŸใฏใƒœใƒƒใƒˆใซDJๆจฉ้™ใ‚’็งป่กŒใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“ใ€‚", - "djNotInChannel": "`{0}`ใฏใƒœใ‚คใ‚นใƒใƒฃใƒณใƒใƒซใซใ„ใพใ›ใ‚“ใ€‚", - "djswap": "DJใฎๅฝนๅ‰ฒใ‚’`{0}`ใซ็งป่กŒใ—ใพใ—ใŸใ€‚", - "voicelinkQueueFull": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“ใŒใ€ใ‚ญใƒฅใƒผๅ†…ใฎๆ›ฒๆ•ฐใŒๆœ€ๅคงๅ€คใฎ`{0}`ใซ้”ใ—ใพใ—ใŸ๏ผ", - "voicelinkOutofList": "ๆœ‰ๅŠนใชใƒˆใƒฉใƒƒใ‚ฏใ‚คใƒณใƒ‡ใƒƒใ‚ฏใ‚นใ‚’ๆŒ‡ๅฎšใ—ใฆใใ ใ•ใ„๏ผ", - "voicelinkDuplicateTrack": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“ใŒใ€ใ“ใฎใƒˆใƒฉใƒƒใ‚ฏใฏๆ—ขใซใ‚ญใƒฅใƒผใซๅญ˜ๅœจใ—ใพใ™ใ€‚", - "decodeError": "ใƒ•ใ‚กใ‚คใƒซใฎใƒ‡ใ‚ณใƒผใƒ‰ไธญใซๅ•้กŒใŒ็™บ็”Ÿใ—ใพใ—ใŸ๏ผ", - "invalidStartTime": "็„กๅŠนใช้–‹ๅง‹ๆ™‚้–“๏ผๆ™‚้–“ใฏ `00:00` ใจ `{0}` ใฎ้–“ใซ่จญๅฎšใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚", - "invalidEndTime": "็„กๅŠนใช็ต‚ไบ†ๆ™‚้–“๏ผๆ™‚้–“ใฏ `00:00` ใจ `{0}` ใฎ้–“ใซ่จญๅฎšใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚", - "invalidTimeOrder": "็ต‚ไบ†ๆ™‚้–“ใฏ้–‹ๅง‹ๆ™‚้–“ใ‚ˆใ‚Šๅคงใใใชใ„ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚", - "setStageAnnounceTemplate": "ๅฎŒไบ†๏ผใ“ใ‚Œใ‹ใ‚‰ใฏใ€ไปŠใ„ใ‚‹ใƒœใ‚คใ‚นใ‚นใƒ†ใƒผใ‚ฟใ‚นใŒใ‚ใชใŸใฎใƒ†ใƒณใƒ—ใƒฌใƒผใƒˆใซๅพ“ใฃใฆๅๅ‰ใŒไป˜ใ‘ใ‚‰ใ‚Œใพใ™ใ€‚ๆ•ฐ็ง’ไปฅๅ†…ใซๆ›ดๆ–ฐใ•ใ‚Œใ‚‹ใฎใ‚’่ฆ‹ใ‚‹ใ“ใจใŒใงใใ‚‹ใฏใšใงใ™ใ€‚", - "createSongRequestChannel": "ๆ›ฒใฎใƒชใ‚ฏใ‚จใ‚นใƒˆใƒใƒฃใƒณใƒใƒซ ({0}) ใŒไฝœๆˆใ•ใ‚Œใพใ—ใŸ๏ผใใฎใƒใƒฃใƒณใƒใƒซใงใ€ๆ›ฒๅใพใŸใฏ URL ใ‚’ไฝฟ็”จใ—ใฆไปปๆ„ใฎๆ›ฒใ‚’ใƒชใ‚ฏใ‚จใ‚นใƒˆใงใใพใ™ใ€‚ใƒœใƒƒใƒˆใฎใƒ—ใƒฌใƒ•ใ‚ฃใƒƒใ‚ฏใ‚นใฏๅฟ…่ฆใ‚ใ‚Šใพใ›ใ‚“ใ€‚" -} \ No newline at end of file + "common": { + "errors": { + "unknown": "โš ๏ธ ใ‚ณใƒžใƒณใƒ‰ใฎๅฎŸ่กŒไธญใซไฝ•ใ‹ใŒ้–“้•ใฃใฆใ„ใพใ™๏ผๅพŒใงใ‚‚ใ†ไธ€ๅบฆ่ฉฆใ—ใฆใใ ใ•ใ„ใ€‚ใพใŸใฏใ€ใ‚ˆใ‚Š่ฉณใ—ใ„ใ‚ตใƒใƒผใƒˆใ‚’ๆฑ‚ใ‚ใ‚‹ใŸใ‚ใซๅฝ“็คพใฎDiscordใ‚ตใƒผใƒใƒผใซๅ‚ๅŠ ใ—ใฆใใ ใ•ใ„ใ€‚", + "missingIntents": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“ใŒใ€ใ“ใฎใ‚ณใƒžใƒณใƒ‰ใฏๅฎŸ่กŒใงใใพใ›ใ‚“ใ€‚ใƒœใƒƒใƒˆใซๅฟ…่ฆใชใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚คใƒณใƒ†ใƒณใƒˆใŒไธ่ถณใ—ใฆใ„ใพใ™๏ผš`({0})`." + }, + "status": { + "enabled": "ๆœ‰ๅŠนๅŒ–", + "disabled": "็„กๅŠนๅŒ–", + "live": "ใƒฉใ‚คใƒ–" + } + }, + "player": { + "errors": { + "nodeReconnect": "ๅ†่ฉฆ่กŒใ—ใฆใใ ใ•ใ„๏ผใƒŽใƒผใƒ‰ใŒๅ†ๆŽฅ็ถšใ—ใŸๅพŒใ€‚", + "noPlaySource": "ๅ†็”Ÿๅฏ่ƒฝใชใ‚ฝใƒผใ‚นใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“๏ผ", + "noPlayer": "ใ“ใฎใ‚ตใƒผใƒใƒผใซใƒ—ใƒฌใ‚คใƒคใƒผใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใ€‚", + "noTrackPlaying": "็พๅœจๅ†็”Ÿไธญใฎๆ›ฒใฏใ‚ใ‚Šใพใ›ใ‚“", + "noTrackFound": "ใใฎใ‚ฏใ‚จใƒชใงๆ›ฒใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใงใ—ใŸ๏ผๆœ‰ๅŠนใชURLใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚" + }, + "dropdown": { + "trackSelect": "ใ‚นใ‚ญใƒƒใƒ—ใ™ใ‚‹ๆ›ฒใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„...", + "filterSelect": "้ฉ็”จใ™ใ‚‹ใƒ•ใ‚ฃใƒซใ‚ฟใƒผใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„..." + }, + "buttons": { + "back": "ๆˆปใ‚‹", + "pause": "ไธ€ๆ™‚ๅœๆญข", + "resume": "ๅ†้–‹", + "skip": "ใ‚นใ‚ญใƒƒใƒ—", + "leave": "้€€ๅ‡บ", + "loop": "ใƒซใƒผใƒ—", + "volumeUp": "้Ÿณ้‡ใ‚’ไธŠใ’ใ‚‹", + "volumeDown": "้Ÿณ้‡ใ‚’ไธ‹ใ’ใ‚‹", + "volumeMute": "ๆถˆ้Ÿณ", + "volumeUnmute": "ๆถˆ้Ÿณใ‚’่งฃ้™ค", + "autoPlay": "่‡ชๅ‹•ๅ†็”Ÿ", + "shuffle": "ใ‚ทใƒฃใƒƒใƒ•ใƒซ", + "forward": "้€ฒใ‚€", + "rewind": "ๆˆปใ‚‹", + "lyrics": "ๆญŒ่ฉž" + }, + "playback": { + "nowplayingDesc": "**็พๅœจๅ†็”Ÿไธญ๏ผš**\n```{0}```", + "nowplayingField": "ๆฌกใซๅ†็”Ÿใ™ใ‚‹ๆ›ฒ๏ผš", + "nowplayingLink": "{0}ใง่ดใ", + "playlistLoad": " ๐ŸŽถ ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ**{0}**ใ‚’ใ‚ญใƒฅใƒผใซ`{1}`ๆ›ฒ่ฟฝๅŠ ใ—ใพใ—ใŸใ€‚", + "trackLoad": "**{2}**ใฎ**[{0}](<{1}>)** (`{3}`)ใ‚’ๅ†็”Ÿใ‚’้–‹ๅง‹ใ™ใ‚‹ใŸใ‚ใซ่ฟฝๅŠ ใ—ใพใ—ใŸใ€‚\n", + "trackLoadPos": "**{2}**ใฎ**[{0}](<{1}>)** (`{3}`)ใ‚’ใ‚ญใƒฅใƒผใฎไฝ็ฝฎ**{4}**ใซ่ฟฝๅŠ ใ—ใพใ—ใŸใ€‚\n" + }, + "controls": { + "pause": { + "error": "ใƒ—ใƒฌใ‚คใƒคใƒผใฏใ™ใงใซไธ€ๆ™‚ๅœๆญขใ—ใฆใ„ใพใ™ใ€‚", + "vote": "{0}ใŒๆ›ฒใ‚’ไธ€ๆ™‚ๅœๆญขใ™ใ‚‹ใ“ใจใซ่ณ›ๆˆใ—ใพใ—ใŸใ€‚[{1}/{2}]", + "success": "{0}ใŒใƒ—ใƒฌใ‚คใƒคใƒผใ‚’ไธ€ๆ™‚ๅœๆญขใ—ใพใ—ใŸใ€‚" + }, + "resume": { + "error": "ใƒ—ใƒฌใ‚คใƒคใƒผใฏไธ€ๆ™‚ๅœๆญขใ—ใฆใ„ใพใ›ใ‚“ใ€‚", + "vote": "{0}ใŒๆ›ฒใ‚’ๅ†้–‹ใ™ใ‚‹ใ“ใจใซ่ณ›ๆˆใ—ใพใ—ใŸใ€‚[{1}/{2}]", + "success": "{0}ใŒใƒ—ใƒฌใ‚คใƒคใƒผใ‚’ๅ†้–‹ใ—ใพใ—ใŸใ€‚" + }, + "shuffle": { + "error": "ใ‚ทใƒฃใƒƒใƒ•ใƒซใ™ใ‚‹ๅ‰ใซใ‚ญใƒฅใƒผใซๆ›ฒใ‚’่ฟฝๅŠ ใ—ใฆใใ ใ•ใ„ใ€‚", + "vote": "{0}ใŒใ‚ญใƒฅใƒผใ‚’ใ‚ทใƒฃใƒƒใƒ•ใƒซใ™ใ‚‹ใ“ใจใซ่ณ›ๆˆใ—ใพใ—ใŸใ€‚[{1}/{2}]", + "success": "ใ‚ญใƒฅใƒผใŒใ‚ทใƒฃใƒƒใƒ•ใƒซใ•ใ‚Œใพใ—ใŸใ€‚" + }, + "skip": { + "error": "ใ‚นใ‚ญใƒƒใƒ—ใ™ใ‚‹ๆ›ฒใฏใ‚ใ‚Šใพใ›ใ‚“ใ€‚", + "vote": "{0}ใŒๆ›ฒใ‚’ใ‚นใ‚ญใƒƒใƒ—ใ™ใ‚‹ใ“ใจใซ่ณ›ๆˆใ—ใพใ—ใŸใ€‚[{1}/{2}]", + "success": "{0}ใŒๆ›ฒใ‚’ใ‚นใ‚ญใƒƒใƒ—ใ—ใพใ—ใŸใ€‚" + }, + "back": { + "vote": "{0}ใŒๅ‰ใฎๆ›ฒใซใ‚นใ‚ญใƒƒใƒ—ใ™ใ‚‹ใ“ใจใซ่ณ›ๆˆใ—ใพใ—ใŸใ€‚[{1}/{2}]", + "success": "`{0}`ใŒๅ‰ใฎๆ›ฒใซใ‚นใ‚ญใƒƒใƒ—ใ—ใพใ—ใŸใ€‚" + }, + "leave": { + "vote": "{0}ใŒใƒ—ใƒฌใ‚คใƒคใƒผใ‚’ๅœๆญขใ™ใ‚‹ใ“ใจใซ่ณ›ๆˆใ—ใพใ—ใŸใ€‚[{1}/{2}]", + "success": "`{0}`ใŒใƒ—ใƒฌใ‚คใƒคใƒผใ‚’ๅœๆญขใ—ใพใ—ใŸใ€‚" + }, + "seek": "ใƒ—ใƒฌใ‚คใƒคใƒผใ‚’ **{0}** ใซ่จญๅฎšใ—ใพใ—ใŸใ€‚", + "repeat": "ใƒชใƒ”ใƒผใƒˆใƒขใƒผใƒ‰ใŒ `{0}` ใซ่จญๅฎšใ•ใ‚Œใพใ—ใŸใ€‚", + "forward": "ใƒ—ใƒฌใ‚คใƒคใƒผใ‚’ **{0}** ใซ้€ฒใ‚ใพใ—ใŸใ€‚", + "rewind": "ใƒ—ใƒฌใ‚คใƒคใƒผใ‚’ **{0}** ใซๆˆปใ—ใพใ—ใŸใ€‚", + "replay": "็พๅœจใฎๆ›ฒใ‚’ใƒชใƒ—ใƒฌใ‚คใ—ใพใ™ใ€‚", + "autoplay": "ใ‚ชใƒผใƒˆใƒ—ใƒฌใ‚คใƒขใƒผใƒ‰ใŒ **{0}** ใซ่จญๅฎšใ•ใ‚Œใพใ—ใŸใ€‚" + } + }, + "voice": { + "connection": { + "noChannel": "ๆŽฅ็ถšใ™ใ‚‹้ŸณๅฃฐใƒใƒฃใƒณใƒใƒซใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚ๆไพ›ใ™ใ‚‹ใ‹ใ€ๅ‚ๅŠ ใ—ใฆใใ ใ•ใ„ใ€‚", + "alreadyConnected": "ใ™ใงใซ้ŸณๅฃฐใƒใƒฃใƒณใƒใƒซใซๆŽฅ็ถšใ—ใฆใ„ใพใ™ใ€‚", + "noPermission": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผ็งใฏใ‚ใชใŸใฎ้Ÿณๅฃฐใƒใƒฃใƒณใƒใƒซใซๅ‚ๅŠ ใพใŸใฏ่ฉฑใ™ใŸใ‚ใฎ่จฑๅฏใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚", + "notInChannel": "{0}ใ•ใ‚“ใ€้Ÿณๅฃฐใ‚ณใƒžใƒณใƒ‰ใ‚’ไฝฟ็”จใ™ใ‚‹ใซใฏ{1}ใซใ„ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚้ŸณๅฃฐใซๆŽฅ็ถšใ—ใฆใ„ใ‚‹ๅ ดๅˆใฏๅ†ๆŽฅ็ถšใ—ใฆใใ ใ•ใ„๏ผ", + "connect": "{0}ใซๆŽฅ็ถšใ—ใพใ—ใŸ" + }, + "stageChannel": { + "setAnnounceTemplate": "ๅฎŒไบ†๏ผใ“ใ‚Œใ‹ใ‚‰ใฏใ€ไปŠใ„ใ‚‹ใƒœใ‚คใ‚นใ‚นใƒ†ใƒผใ‚ฟใ‚นใŒใ‚ใชใŸใฎใƒ†ใƒณใƒ—ใƒฌใƒผใƒˆใซๅพ“ใฃใฆๅๅ‰ใŒไป˜ใ‘ใ‚‰ใ‚Œใพใ™ใ€‚ๆ•ฐ็ง’ไปฅๅ†…ใซๆ›ดๆ–ฐใ•ใ‚Œใ‚‹ใฎใ‚’่ฆ‹ใ‚‹ใ“ใจใŒใงใใ‚‹ใฏใšใงใ™ใ€‚" + } + }, + "permissions": { + "noCreatePermission": "ใ”ใ‚ใ‚“ใชใ•ใ„๏ผๆ›ฒใƒชใ‚ฏใ‚จใ‚นใƒˆใƒใƒฃใƒณใƒใƒซใ‚’ไฝœๆˆใ™ใ‚‹ๆจฉ้™ใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚", + "missingPosition": "DJใพใŸใฏ็ฎก็†่€…ใฎใฟใŒไฝ็ฝฎใ‚’ๅค‰ๆ›ดใงใใพใ™ใ€‚", + "missingMode": "DJใพใŸใฏ็ฎก็†่€…ใฎใฟใŒใƒซใƒผใƒ—ใƒขใƒผใƒ‰ใ‚’ๅˆ‡ใ‚Šๆ›ฟใˆใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚", + "missingQueue": "DJใพใŸใฏ็ฎก็†่€…ใฎใฟใŒใ‚ญใƒฅใƒผใ‹ใ‚‰ใƒˆใƒฉใƒƒใ‚ฏใ‚’ๅ‰Š้™คใงใใพใ™ใ€‚", + "missingAutoPlay": "DJใพใŸใฏ็ฎก็†่€…ใฎใฟใŒใ‚ชใƒผใƒˆใƒ—ใƒฌใ‚คใƒขใƒผใƒ‰ใ‚’ๆœ‰ๅŠนๅŒ–ใพใŸใฏ็„กๅŠนๅŒ–ใงใใพใ™๏ผ", + "missingFunction": "DJใพใŸใฏ็ฎก็†่€…ใฎใฟใŒใ“ใฎๆฉŸ่ƒฝใ‚’ไฝฟ็”จใงใใพใ™ใ€‚", + "notDj": "DJใงใฏใ‚ใ‚Šใพใ›ใ‚“ใ€‚็พๅœจใฎDJใฏ{0}ใงใ™ใ€‚", + "djToSelf": "่‡ชๅˆ†่‡ช่บซใพใŸใฏใƒœใƒƒใƒˆใซDJๆจฉ้™ใ‚’็งป่กŒใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“ใ€‚", + "djNotInChannel": "`{0}`ใฏใƒœใ‚คใ‚นใƒใƒฃใƒณใƒใƒซใซใ„ใพใ›ใ‚“ใ€‚", + "djSwapped": "DJใฎๅฝนๅ‰ฒใ‚’`{0}`ใซ็งป่กŒใ—ใพใ—ใŸใ€‚" + }, + "settings": { + "actions": { + "languageNotFound": "่จ€่ชžใƒ‘ใƒƒใ‚ฏใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใ€‚ๆ—ขๅญ˜ใฎ่จ€่ชžใƒ‘ใƒƒใ‚ฏใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "languageChanged": "ใ€Œ{0}ใ€่จ€่ชžใƒ‘ใƒƒใ‚ฏใซๆญฃๅธธใซๅค‰ๆ›ดใ—ใพใ—ใŸใ€‚", + "prefixSet": "ๅฎŒไบ†๏ผใ‚ใชใŸใฎใ‚ตใƒผใƒใƒผใฎใƒ—ใƒฌใƒ•ใ‚ฃใƒƒใ‚ฏใ‚นใฏไปŠใ‚„ใ€Œ{0}ใ€ใงใ™ใ€‚ `{1}ping`ใ‚’ๅฎŸ่กŒใ—ใฆใƒ†ใ‚นใƒˆใ—ใฆใฟใฆใใ ใ•ใ„ใ€‚", + "djSet": "{0}ใซDJใ‚’่จญๅฎšใ—ใพใ—ใŸใ€‚", + "queueModeSet": "ใ‚ญใƒฅใƒผใƒขใƒผใƒ‰ใ‚’ใ€Œ{0}ใ€ใซ่จญๅฎšใ—ใพใ—ใŸใ€‚", + "mode247": "ไปŠใ€24/7ใƒขใƒผใƒ‰ใฏใ€Œ{0}ใ€ใงใ™ใ€‚", + "bypassVote": "ไปŠใ€ๆŠ•็ฅจใ‚ทใ‚นใƒ†ใƒ ใฏใ€Œ{0}ใ€ใซใชใ‚Šใพใ—ใŸใ€‚", + "volumeSet": "้Ÿณ้‡ใ‚’ใ€Œ{0}๏ผ…ใ€ใซ่จญๅฎšใ—ใพใ—ใŸใ€‚", + "controllerToggled": "ไปŠใ€้Ÿณๆฅฝใ‚ณใƒณใƒˆใƒญใƒผใƒฉใƒผใฏใ€Œ{0}ใ€ใงใ™ใ€‚", + "duplicateTrackToggled": "ไปŠใ€ใ‚ญใƒฅใƒผๅ†…ใฎ้‡่ค‡ใƒˆใƒฉใƒƒใ‚ฏใ‚’้˜ฒๆญขใ™ใ‚‹ใŸใ‚ใฎ่จญๅฎšใฏใ€Œ{0}ใ€ใงใ™ใ€‚", + "controllerMsgToggled": "็พๅœจใ€้Ÿณๆฅฝใ‚ณใƒณใƒˆใƒญใƒผใƒฉใƒผใ‹ใ‚‰ `{0}` ไปถใฎใƒกใƒƒใ‚ปใƒผใ‚ธใŒใ‚ใ‚Šใพใ™ใ€‚", + "silentMsgToggled": "ใ‚ใชใŸใฏ{0}ใ‚ตใ‚คใƒฌใƒณใƒˆใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ๆŒใฃใฆใ„ใพใ™ใ€‚" + }, + "menu": "ใ‚ตใƒผใƒใƒผ่จญๅฎš| {0}", + "basic": { + "title": "โค๏ธ ๅŸบๆœฌๆƒ…ๅ ฑ๏ผš", + "value": "```ใƒ—ใƒฌใƒ•ใ‚ฃใƒƒใ‚ฏใ‚น๏ผš{0}\n่จ€่ชž๏ผš{1}\n้Ÿณๆฅฝใ‚ณใƒณใƒˆใƒญใƒผใƒฉใƒผใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹๏ผš{2}\nDJใƒญใƒผใƒซ๏ผš@{3}\nๆŠ•็ฅจใƒใ‚คใƒ‘ใ‚น๏ผš{4}\n24/7๏ผš{5}\nใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใƒœใƒชใƒฅใƒผใƒ ๏ผš{6}๏ผ…\nๅ†็”Ÿๆ™‚้–“๏ผš{7}```" + }, + "queue": { + "title": "๐Ÿ”— ใ‚ญใƒฅใƒผๆƒ…ๅ ฑ๏ผš", + "value": "```ใ‚ญใƒฅใƒผใƒขใƒผใƒ‰๏ผš{0}\nๆœ€ๅคงๆ›ฒๆ•ฐ๏ผš{1}\n้‡่ค‡ใƒˆใƒฉใƒƒใ‚ฏใ‚’่จฑๅฏใ™ใ‚‹๏ผš{2}```" + }, + "voice": { + "title": "๐ŸŽค ้Ÿณๅฃฐใ‚นใƒ†ใƒผใ‚ฟใ‚นๆƒ…ๅ ฑ๏ผš" + }, + "permissions": { + "title": "โœจ ๆจฉ้™๏ผš", + "value": "```{0} ็ฎก็†่€…\n{1} ใ‚ฎใƒซใƒ‰็ฎก็†\n{2} ใƒใƒฃใƒณใƒใƒซ็ฎก็†\n{3} ใƒกใƒƒใ‚ปใƒผใ‚ธ็ฎก็†```" + } + }, + "ping": { + "title1": "ใƒœใƒƒใƒˆๆƒ…ๅ ฑ๏ผš", + "title2": "ใƒ—ใƒฌใƒผใƒคใƒผๆƒ…ๅ ฑ๏ผš", + "field1": "```ใ‚ทใƒฃใƒผใƒ‰ID๏ผš{0}/{1}\nใ‚ทใƒฃใƒผใƒ‰ใƒฌใ‚คใƒ†ใƒณใ‚ท๏ผš{2:.3f}s {3}\nใƒชใƒผใ‚ธใƒงใƒณ๏ผš{4}```", + "field2": "```ใƒŽใƒผใƒ‰๏ผš{0} - {1:.3f}s\nใƒ—ใƒฌใ‚คใƒคใƒผๆ•ฐ๏ผš{2}\n้Ÿณๅฃฐใƒชใƒผใ‚ธใƒงใƒณ๏ผš{3}```" + }, + "effects": { + "added": "`{0}` ใƒ•ใ‚ฃใƒซใ‚ฟใƒผใ‚’้ฉ็”จใ—ใพใ™ใ€‚", + "cleared": "ๅŠนๆžœ้ŸณใŒใ‚ฏใƒชใ‚ขใ•ใ‚Œใพใ—ใŸ๏ผ", + "tagInUse": "ใ“ใฎใ‚ตใ‚ฆใƒณใƒ‰ใ‚จใƒ•ใ‚งใ‚ฏใƒˆใฏใ™ใงใซไฝฟ็”จใ•ใ‚Œใฆใ„ใพใ™๏ผๅ‰Š้™คใ™ใ‚‹ใซใฏ/cleareffect ใ‚’ไฝฟ็”จใ—ใฆใใ ใ•ใ„ใ€‚" + }, + "playlist": { + "view": { + "title": "{0}ใฎใ™ในใฆใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ", + "headers": "ID:,ๆ™‚้–“:,ๅๅ‰:,ใƒˆใƒฉใƒƒใ‚ฏ:", + "footer": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใ‚’ใ‚ญใƒฅใƒผใซ่ฟฝๅŠ ใ™ใ‚‹ใซใฏใ€/playlist play [playlist]ใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚", + "detailTitle": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใƒ“ใƒฅใƒผใ‚ขใƒผ", + "detailDesc": "```ๅๅ‰: {0} [{1}]\nใƒˆใƒผใ‚ฟใƒซใƒˆใƒฉใƒƒใ‚ฏ: {2}\nใ‚ชใƒผใƒŠใƒผ: {3}\nใ‚ฟใ‚คใƒ—: {4}\n```", + "permsValue": "๐Ÿ“– ่ชญใฟ่พผใฟ: โœ“ โœ๐Ÿฝ ๆ›ธใ่พผใฟ: {0} ๐Ÿ—‘๏ธ ๅ‰Š้™ค: {1}", + "permsValue2": "๐Ÿ“– ่ชญใฟ่พผใฟ: {0}", + "trackList": "ใƒˆใƒฉใƒƒใ‚ฏ", + "footer2": "ใƒˆใƒผใ‚ฟใƒซใƒ€ใƒฌใƒผใ‚ทใƒงใƒณ: {0}", + "playlistSelect": "่กจ็คบใ™ใ‚‹ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„..." + }, + "errors": { + "notFound": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[{0}]ใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใ€‚ใ™ในใฆใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใ‚’่กจ็คบใ™ใ‚‹ใซใฏใ€/playlist viewใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚", + "noAccess": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผใ“ใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใซใ‚ขใ‚ฏใ‚ปใ‚นใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“๏ผ", + "noTrack": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[{0}]ใซใฏใƒˆใƒฉใƒƒใ‚ฏใŒใ‚ใ‚Šใพใ›ใ‚“ใ€‚", + "notAllowed": "ใƒชใƒณใ‚ฏใพใŸใฏๅ…ฑๆœ‰ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใงใฏใ€ใ“ใฎใ‚ณใƒžใƒณใƒ‰ใฏ่จฑๅฏใ•ใ‚Œใฆใ„ใพใ›ใ‚“ใ€‚", + "nameOverLimit": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใฎๅๅ‰ใฏ10ๆ–‡ๅญ—ใ‚’่ถ…ใˆใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“ใ€‚", + "sameName": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผใ“ใฎๅๅ‰ใฏๆ–ฐใ—ใ„ๅๅ‰ใจๅŒใ˜ใงใ‚ใฃใฆใฏใชใ‚Šใพใ›ใ‚“ใ€‚", + "deleteDefault": "ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใ‚’ๅ‰Š้™คใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“ใ€‚", + "limitReached": " {0}ๅ€‹ไปฅไธŠใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใ‚’ไฝœๆˆใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“๏ผ", + "exists": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[{0}]ใฏใ™ใงใซๅญ˜ๅœจใ—ใพใ™ใ€‚", + "invalidUrl": "ๆœ‰ๅŠนใชใƒชใƒณใ‚ฏใพใŸใฏๅ…ฌ้–‹SpotifyใพใŸใฏYouTubeใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใƒชใƒณใ‚ฏใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚", + "trackLimitReached": "ใ“ใฎๅˆถ้™ใซ้”ใ—ใพใ—ใŸ๏ผใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใซใฏ{0}ๆ›ฒใ—ใ‹่ฟฝๅŠ ใงใใพใ›ใ‚“ใ€‚", + "playlistLinkNotAllowed": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใƒชใƒณใ‚ฏใ‚’ไฝฟ็”จใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“ใ€‚", + "streamNotAllowed": "ใ‚นใƒˆใƒชใƒผใƒŸใƒณใ‚ฐใƒ“ใƒ‡ใ‚ชใ‚’ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใซ่ฟฝๅŠ ใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“ใ€‚", + "positionNotFound": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[{1}]ใ‹ใ‚‰ไฝ็ฝฎ{0}ใ‚’่ฆ‹ใคใ‘ใ‚‹ใ“ใจใŒใงใใพใ›ใ‚“ใงใ—ใŸ๏ผ", + "streaming": "ใ‚นใƒˆใƒชใƒผใƒŸใƒณใ‚ฐใƒ“ใƒ‡ใ‚ชใ‚’ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใซ่ฟฝๅŠ ใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“๏ผ", + "track": "ใƒˆใƒฉใƒƒใ‚ฏใ‚’ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใซ่ฟฝๅŠ ใ™ใ‚‹้š›ใซๅ•้กŒใŒ็™บ็”Ÿใ—ใพใ—ใŸ๏ผ", + "trackLimited": "ๅˆถ้™ใซ้”ใ—ใพใ—ใŸ๏ผใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใซใฏ{0}ๆ›ฒใ—ใ‹่ฟฝๅŠ ใงใใพใ›ใ‚“ใ€‚", + "trackRepeated": "ใ™ใงใซใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใซๅŒใ˜ใƒˆใƒฉใƒƒใ‚ฏใŒใ‚ใ‚Šใพใ™๏ผ" + }, + "actions": { + "play": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[{0}]ใ‚’ใ‚ญใƒฅใƒผใซ{1}ๆ›ฒ่ฟฝๅŠ ใ—ใพใ—ใŸใ€‚", + "removed": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[{0}]ใ‚’ๅ‰Š้™คใ—ใพใ—ใŸใ€‚", + "created": "{0}ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใ‚’ไฝœๆˆใ—ใพใ—ใŸใ€‚่ฉณ็ดฐใซใคใ„ใฆใฏใ€/playlist viewใ‚’ๅ…ฅๅŠ›ใ—ใฆใใ ใ•ใ„ใ€‚", + "renamed": "{0}ใ‚’{1}ใซๅๅ‰ใ‚’ๅค‰ๆ›ดใ—ใพใ—ใŸใ€‚", + "trackRemoved": "๐Ÿ‘‹ {1}ใ•ใ‚“ใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[{2}]ใ‹ใ‚‰**{0}**ใ‚’ๅ‰Š้™คใ—ใพใ—ใŸใ€‚", + "cleared": "ใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[{0}]ใ‚’ๆญฃๅธธใซใ‚ฏใƒชใ‚ขใ—ใพใ—ใŸใ€‚", + "trackAdded": "โค๏ธ **{0}**ใ‚’{1}ใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆ[`{2}`]ใซ่ฟฝๅŠ ใ—ใพใ—ใŸ๏ผ" + }, + "sharing": { + "sendErrorPlayer": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผ่‡ชๅˆ†ใซๆ‹›ๅพ…็Šถใ‚’้€ไฟกใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“ใ€‚", + "sendErrorBot": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผใƒœใƒƒใƒˆใซๆ‹›ๅพ…็Šถใ‚’้€ไฟกใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“ใ€‚", + "belongs": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผใ“ใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใฏ<@{0}>ใ•ใ‚“ใฎใ‚‚ใฎใงใ™ใ€‚", + "alreadyShared": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผใ“ใฎใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใฏ{0}ใ•ใ‚“ใจๅ…ฑๆœ‰ใ•ใ‚Œใฆใ„ใพใ™ใ€‚", + "alreadySent": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผไปฅๅ‰ใซๆ‹›ๅพ…็Šถใ‚’้€ไฟกใ—ใพใ—ใŸใ€‚", + "noAccount": "{0}ใ•ใ‚“ใฏใƒ—ใƒฌใ‚คใƒชใ‚นใƒˆใ‚ขใ‚ซใ‚ฆใƒณใƒˆใ‚’ไฝœๆˆใ—ใฆใ„ใพใ›ใ‚“ใ€‚", + "invitationSent": "{0}ใ•ใ‚“ใซๆ‹›ๅพ…็Šถใ‚’้€ไฟกใ—ใพใ—ใŸใ€‚" + }, + "inbox": { + "full": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“๏ผ{0}ใ•ใ‚“ใฎๅ—ไฟกใƒˆใƒฌใ‚คใฏใ„ใฃใฑใ„ใงใ™ใ€‚", + "noMessages": "ๅ—ไฟกใƒˆใƒฌใ‚คใซใƒกใƒƒใ‚ปใƒผใ‚ธใฏใ‚ใ‚Šใพใ›ใ‚“ใ€‚" + } + }, + "search": { + "noLinkSupport": "ๆคœ็ดขใ‚ณใƒžใƒณใƒ‰ใฏใƒชใƒณใ‚ฏใ‚’ใ‚ตใƒใƒผใƒˆใ—ใฆใ„ใพใ›ใ‚“๏ผ", + "title": "ๆคœ็ดขใ‚ฏใ‚จใƒช๏ผš{0}", + "desc": "โžฅ ใƒ—ใƒฉใƒƒใƒˆใƒ•ใ‚ฉใƒผใƒ ๏ผš{0} **{1}**\nโžฅ ็ตๆžœ๏ผš**{2}**\n\n{3}", + "wait": "ใ‚ญใƒฅใƒผใซ่ฟฝๅŠ ใ—ใŸใ„ๆ›ฒใ‚’้ธๆŠžใ—ใฆใใ ใ•ใ„ใ€‚", + "success": "ๆ›ฒใŒใ‚ญใƒฅใƒผใซ่ฟฝๅŠ ใ•ใ‚Œใพใ—ใŸใ€‚" + }, + "voting": { + "voted": "ๆŠ•็ฅจใ—ใพใ—ใŸ๏ผ" + }, + "time": { + "formatError": "ๆ™‚้–“ใฎๅฝขๅผใŒ้–“้•ใฃใฆใ„ใพใ™ใ€‚ไพ‹๏ผš2:42ใพใŸใฏ12:39:31", + "invalidStartTime": "็„กๅŠนใช้–‹ๅง‹ๆ™‚้–“๏ผๆ™‚้–“ใฏ `00:00` ใจ `{0}` ใฎ้–“ใซ่จญๅฎšใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚", + "invalidEndTime": "็„กๅŠนใช็ต‚ไบ†ๆ™‚้–“๏ผๆ™‚้–“ใฏ `00:00` ใจ `{0}` ใฎ้–“ใซ่จญๅฎšใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚" + }, + "lyrics": { + "notFound": "ๆญŒ่ฉžใŒ่ฆ‹ใคใ‹ใ‚Šใพใ›ใ‚“ใงใ—ใŸใ€‚/lyrics <ๆ›ฒๅ> <ใ‚ขใƒผใƒ†ใ‚ฃใ‚นใƒˆ>ใจๅ…ฅๅŠ›ใ—ใฆๆญŒ่ฉžใ‚’ๆคœ็ดขใ—ใฆใใ ใ•ใ„ใ€‚" + }, + "queue": { + "management": { + "title": "ใ‚ญใƒฅใƒผใซ่ฟฝๅŠ ใ•ใ‚ŒใŸๆ›ฒ๏ผš", + "cleared": "`{0}`ใฎใ™ในใฆใฎใƒˆใƒฉใƒƒใ‚ฏใ‚’ใ‚ฏใƒชใ‚ขใ—ใพใ—ใŸใ€‚", + "removed": "`{0}`ใฎใƒˆใƒฉใƒƒใ‚ฏใŒใ‚ญใƒฅใƒผใ‹ใ‚‰ๅ‰Š้™คใ•ใ‚Œใพใ—ใŸใ€‚", + "swapped": "`{0}`ใจ`{1}`ใŒไบคๆ›ใ•ใ‚Œใพใ—ใŸใ€‚", + "moved": "`{0}`ใ‚’`{1}`ใซ็งปๅ‹•ใ—ใพใ—ใŸใ€‚" + }, + "view": { + "historyTitle": "ๅ†็”Ÿๅฑฅๆญด๏ผš", + "title": "้Ÿณๆฅฝใ‚ญใƒฅใƒผ", + "desc": "**็พๅœจๅ†็”Ÿไธญ๏ผš[ใ“ใ“ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆ่ดใ]({0}) โฎฏ**\n{1}" + }, + "errors": { + "queueFull": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“ใŒใ€ใ‚ญใƒฅใƒผๅ†…ใฎๆ›ฒๆ•ฐใŒๆœ€ๅคงๅ€คใฎ`{0}`ใซ้”ใ—ใพใ—ใŸ๏ผ", + "outOfList": "ๆœ‰ๅŠนใชใƒˆใƒฉใƒƒใ‚ฏใ‚คใƒณใƒ‡ใƒƒใ‚ฏใ‚นใ‚’ๆŒ‡ๅฎšใ—ใฆใใ ใ•ใ„๏ผ", + "duplicateTrack": "็”ณใ—่จณใ‚ใ‚Šใพใ›ใ‚“ใŒใ€ใ“ใฎใƒˆใƒฉใƒƒใ‚ฏใฏๆ—ขใซใ‚ญใƒฅใƒผใซๅญ˜ๅœจใ—ใพใ™ใ€‚" + } + }, + "songRequest": { + "channelCreated": "ๆ›ฒใฎใƒชใ‚ฏใ‚จใ‚นใƒˆใƒใƒฃใƒณใƒใƒซ ({0}) ใŒไฝœๆˆใ•ใ‚Œใพใ—ใŸ๏ผใใฎใƒใƒฃใƒณใƒใƒซใงใ€ๆ›ฒๅใพใŸใฏ URL ใ‚’ไฝฟ็”จใ—ใฆไปปๆ„ใฎๆ›ฒใ‚’ใƒชใ‚ฏใ‚จใ‚นใƒˆใงใใพใ™ใ€‚ใƒœใƒƒใƒˆใฎใƒ—ใƒฌใƒ•ใ‚ฃใƒƒใ‚ฏใ‚นใฏๅฟ…่ฆใ‚ใ‚Šใพใ›ใ‚“ใ€‚" + }, + "pagination": { + "prev": "ๅ‰ใธ", + "next": "ๆฌกใธ", + "play": "ๅ†็”Ÿ", + "share": "ๅ…ฑๆœ‰", + "export": "ใ‚จใ‚ฏใ‚นใƒใƒผใƒˆ", + "delete": "ๅ‰Š้™ค", + "page": { + "title": "ใƒšใƒผใ‚ธ็•ชๅทใ‚’ๅ…ฅๅŠ›", + "field": "ใƒšใƒผใ‚ธ็•ชๅท" + } + } +} diff --git a/langs/KO.json b/langs/KO.json index fe49dfc..e105467 100644 --- a/langs/KO.json +++ b/langs/KO.json @@ -1,172 +1,262 @@ { - "unknownException": "โš ๏ธ ๋ช…๋ น์–ด ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค! ๋‚˜์ค‘์— ๋‹ค์‹œ ์‹œ๋„ํ•˜๊ฑฐ๋‚˜ ์ถ”๊ฐ€ ์ง€์›์„ ์œ„ํ•ด ๋””์Šค์ฝ”๋“œ ์„œ๋ฒ„์— ์ฐธ์—ฌํ•˜์‹ญ์‹œ์˜ค.", - "enabled": "ํ™œ์„ฑํ™”๋จ", - "disabled": "๋น„ํ™œ์„ฑํ™”๋จ", - "nodeReconnect": "๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”! ๋…ธ๋“œ๊ฐ€ ๋‹ค์‹œ ์—ฐ๊ฒฐ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์ฃผ์„ธ์š”.", - "noChannel": "์—ฐ๊ฒฐํ•  ์Œ์„ฑ ์ฑ„๋„์ด ์—†์Šต๋‹ˆ๋‹ค. ํ•˜๋‚˜๋ฅผ ์ œ๊ณตํ•˜๊ฑฐ๋‚˜ ์ฐธ์—ฌํ•˜์‹ญ์‹œ์˜ค.", - "alreadyConnected": "์ด๋ฏธ ์Œ์„ฑ ์ฑ„๋„์— ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.", - "noPermission": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์Œ์„ฑ ์ฑ„๋„์— ์ฐธ์—ฌํ•˜๊ฑฐ๋‚˜ ๋งํ•  ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.", - "noCreatePermission": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ๋…ธ๋ž˜ ์š”์ฒญ ์ฑ„๋„์„ ์ƒ์„ฑํ•  ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.", - "noPlaySource": "์žฌ์ƒ ๊ฐ€๋Šฅํ•œ ์†Œ์Šค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!", - "noPlayer": "์ด ์„œ๋ฒ„์—์„œ ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", - "notVote": "์ด ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด ํˆฌํ‘œํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค! ์ž์„ธํ•œ ๋‚ด์šฉ์€ `/vote`๋ฅผ ์ž…๋ ฅํ•˜์‹ญ์‹œ์˜ค.", - "missingIntents": "์ฃ„์†กํ•˜์ง€๋งŒ ์ด ๋ช…๋ น์„ ์‹คํ–‰ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ด‡์— ํ•„์š”ํ•œ ์š”์ฒญ ์˜๋„๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค:`({0})`.", - "languageNotFound": "์–ธ์–ด ํŒฉ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค! ๊ธฐ์กด ์–ธ์–ด ํŒฉ์„ ์„ ํƒํ•˜์‹ญ์‹œ์˜ค.", - "changedLanguage": "์„ฑ๊ณต์ ์œผ๋กœ `{0}` ์–ธ์–ด ํŒฉ์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "setPrefix": "์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ์ด ์„œ๋ฒ„์—์„œ ๋‚ด ์ ‘๋‘์‚ฌ๋Š” ์ด์ œ `{0}`์ž…๋‹ˆ๋‹ค. `{1}ping`์„ ์‹คํ–‰ํ•˜์—ฌ ํ…Œ์ŠคํŠธํ•ด๋ณด์„ธ์š”.", - "setDJ": "DJ๋ฅผ {0}(์œผ)๋กœ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.", - "setQueue": "ํ ๋ชจ๋“œ๋ฅผ `{0}`(์œผ)๋กœ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.", - "247": "์ด์ œ 24/7 ๋ชจ๋“œ์—์„œ `{0}`์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "bypassVote": "์ด์ œ `{0}` ํˆฌํ‘œ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", - "setVolume": "๋ณผ๋ฅจ์„ `{0}`%๋กœ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.", - "toggleController": "์ด์ œ ์Œ์•… ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ `{0}`(์œผ)๋กœ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "toggleDuplicateTrack": "์ด์ œ ๋Œ€๊ธฐ์—ด์—์„œ ์ค‘๋ณต๋œ ํŠธ๋ž™์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด `{0}`(์œผ)๋กœ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "toggleControllerMsg": "์ด์ œ ์Œ์•… ์ปจํŠธ๋กค๋Ÿฌ๋กœ๋ถ€ํ„ฐ `{0}` ๋ฉ”์‹œ์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.", - "toggleSilentMsg": "๋‹น์‹ ์€ {0} ์นจ๋ฌต ๋ฉ”์‹œ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.", - "settingsMenu": "์„œ๋ฒ„ ์„ค์ • | {0}", - "settingsTitle": "โค๏ธ ๊ธฐ๋ณธ ์ •๋ณด:", - "settingsValue": "```์ ‘๋‘์‚ฌ: {0}\n์–ธ์–ด: {1}\n์Œ์•… ์ปจํŠธ๋กค๋Ÿฌ ์‚ฌ์šฉ: {2}\nDJ ์—ญํ• : @{3}\nํˆฌํ‘œ ์šฐํšŒ: {4}\n24/7 ๋ชจ๋“œ: {5}\n๊ธฐ๋ณธ ๋ณผ๋ฅจ: {6}%\n์žฌ์ƒ ์‹œ๊ฐ„: {7}```", - "settingsTitle2": "๐Ÿ”— ๋Œ€๊ธฐ์—ด ์ •๋ณด:", - "settingsValue2": "```ํ ๋ชจ๋“œ: {0}\n์ตœ๋Œ€ ๋…ธ๋ž˜: {1}\n์ค‘๋ณต๋œ ํŠธ๋ž™ ํ—ˆ์šฉ: {2}```", - "settingsTitle3": "๐ŸŽค ์Œ์„ฑ ์ƒํƒœ ์ •๋ณด:", - "settingsPermTitle": "โœจ ๊ถŒํ•œ:", - "settingsPermValue": "```{0} ๊ด€๋ฆฌ์ž\n{1} ์„œ๋ฒ„ ๊ด€๋ฆฌ\n{2} ์ฑ„๋„ ๊ด€๋ฆฌ\n{3} ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ```", - "pingTitle1": "๋ด‡ ์ •๋ณด:", - "pingTitle2": "ํ”Œ๋ ˆ์ด์–ด ์ •๋ณด:", - "pingField1": "```์‰ฌ๋“œ ID: {0}/{1}\n์‰ฌ๋“œ ๋Œ€๊ธฐ ์‹œ๊ฐ„: {2:.3f}s {3}\n์ง€์—ญ: {4}```", - "pingField2": "```๋…ธ๋“œ: {0} - {1:.3f}s\nํ”Œ๋ ˆ์ด์–ด: {2}\n์Œ์„ฑ ์ง€์—ญ: {3}```", - "addEffect": "`{0}` ํ•„ํ„ฐ๋ฅผ ์ ์šฉํ•˜์„ธ์š”.", - "clearEffect": "ํšจ๊ณผ๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", - "filterTagAlreadyInUse": "์ด ํ•„ํ„ฐ๋Š” ์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๋‹ค! ์‚ญ์ œํ•˜๋ ค๋ฉด /cleareffect ๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค.", - "playlistViewTitle": "๐Ÿ“œ {0}์˜ ๋ชจ๋“  ์žฌ์ƒ ๋ชฉ๋ก", - "playlistViewHeaders": "ID:,์‹œ๊ฐ„:,์ด๋ฆ„:,ํŠธ๋ž™:", - "playlistFooter": "/playlist play [์žฌ์ƒ ๋ชฉ๋ก]์„ ์ž…๋ ฅํ•˜์—ฌ ์žฌ์ƒ ๋ชฉ๋ก์„ ๋Œ€๊ธฐ์—ด์— ์ถ”๊ฐ€ํ•˜์„ธ์š”.", - "playlistNotFound": "์žฌ์ƒ ๋ชฉ๋ก [{0}]์„(๋ฅผ) ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์žฌ์ƒ ๋ชฉ๋ก์„ ๋ณด๋ ค๋ฉด /playlist view๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", - "playlistNotAccess": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์ด ์žฌ์ƒ ๋ชฉ๋ก์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!", - "playlistNoTrack": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์žฌ์ƒ ๋ชฉ๋ก [{0}]์— ํŠธ๋ž™์ด ์—†์Šต๋‹ˆ๋‹ค.", - "playlistNotAllow": "์ด ๋ช…๋ น์–ด๋Š” ์—ฐ๊ฒฐ๋œ ๋ฐ ๊ณต์œ ๋œ ์žฌ์ƒ ๋ชฉ๋ก์—์„œ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", - "playlistPlay": "์žฌ์ƒ ๋ชฉ๋ก [{0}]์„(๋ฅผ) ๋Œ€๊ธฐ์—ด์— {1}๊ณก ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.", - "playlistOverText": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์žฌ์ƒ ๋ชฉ๋ก์˜ ์ด๋ฆ„์€ 10์ž๋ฅผ ์ดˆ๊ณผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", - "playlistSameName": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์ด ์ด๋ฆ„์€ ์ƒˆ ์ด๋ฆ„๊ณผ ๋™์ผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", - "playlistDeleteError": "๊ธฐ๋ณธ ์žฌ์ƒ ๋ชฉ๋ก์€ ์‚ญ์ œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", - "playlistRemove": "์žฌ์ƒ ๋ชฉ๋ก [{0}]์„(๋ฅผ) ์‚ญ์ œํ–ˆ์Šต๋‹ˆ๋‹ค.", - "playlistSendErrorPlayer": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์ž์‹ ์—๊ฒŒ ์ดˆ๋Œ€์žฅ์„ ๋ณด๋‚ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", - "playlistSendErrorBot": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ๋ด‡์—๊ฒŒ ์ดˆ๋Œ€์žฅ์„ ๋ณด๋‚ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", - "playlistBelongs": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์ด ์žฌ์ƒ ๋ชฉ๋ก์€ <@{0}>๋‹˜์˜ ๊ฒƒ์ž…๋‹ˆ๋‹ค.", - "playlistShare": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์ด ์žฌ์ƒ ๋ชฉ๋ก์€ {0}๊ณผ(์™€) ๊ณต์œ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "playlistSent": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์ด๋ฏธ ์ดˆ๋Œ€์žฅ์„ ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค.", - "noPlaylistAcc": "{0}๋‹˜์€ ์žฌ์ƒ ๋ชฉ๋ก ๊ณ„์ •์„ ๋งŒ๋“ค์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.", - "overPlaylistCreation": "๋” ์ด์ƒ {0}๊ฐœ ์ด์ƒ์˜ ์žฌ์ƒ ๋ชฉ๋ก์„ ๋งŒ๋“ค ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!", - "playlistExists": "์žฌ์ƒ ๋ชฉ๋ก [{0}]์ด(๊ฐ€) ์ด๋ฏธ ์žˆ์Šต๋‹ˆ๋‹ค.", - "playlistNotInvalidUrl": "์œ ํšจํ•œ ๋งํฌ ๋˜๋Š” ๊ณต๊ฐœ Spotify ๋˜๋Š” YouTube ์žฌ์ƒ ๋ชฉ๋ก ๋งํฌ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", - "playlistCreated": "{0} ์žฌ์ƒ ๋ชฉ๋ก์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ /playlist view๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", - "playlistRenamed": "{0}์„(๋ฅผ) {1}(์œผ)๋กœ ์ด๋ฆ„์„ ๋ฐ”๊ฟจ์Šต๋‹ˆ๋‹ค.", - "playlistLimitTrack": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์žฌ์ƒ ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š” ๋…ธ๋ž˜๋Š” {0}๊ณก๊นŒ์ง€์ž…๋‹ˆ๋‹ค.", - "playlistPlaylistLink": "์žฌ์ƒ ๋ชฉ๋ก ๋งํฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", - "playlistStream": "์ŠคํŠธ๋ฆฌ๋ฐ ๋น„๋””์˜ค๋ฅผ ์žฌ์ƒ ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", - "playlistPositionNotFound": "์žฌ์ƒ ๋ชฉ๋ก [{1}]์—์„œ ์œ„์น˜ {0}์„(๋ฅผ) ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!", - "playlistRemoved": "๐Ÿ‘‹ {1}๋‹˜์˜ ์žฌ์ƒ ๋ชฉ๋ก [{2}]์—์„œ **{0}**์„(๋ฅผ) ์ œ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค.", - "playlistClear": "์žฌ์ƒ ๋ชฉ๋ก [{0}]์„(๋ฅผ) ์„ฑ๊ณต์ ์œผ๋กœ ์ง€์› ์Šต๋‹ˆ๋‹ค.", - "playlistView": "์žฌ์ƒ ๋ชฉ๋ก ๋ทฐ์–ด", - "playlistViewDesc": "```์ด๋ฆ„ | ID: {0} | {1}\n์ด ํŠธ๋ž™: {2}\n์†Œ์œ ์ž: {3}\n์œ ํ˜•: {4}\n```", - "playlistViewPermsValue": "๐Ÿ“– ์ฝ๊ธฐ: โœ“ โœ๐Ÿฝ ์“ฐ๊ธฐ: {0} ๐Ÿ—‘๏ธ ์‚ญ์ œ: {1}", - "playlistViewPermsValue2": "๐Ÿ“– ์ฝ๊ธฐ: {0}", - "playlistViewTrack": "ํŠธ๋ž™", - "playlistViewPage": "ํŽ˜์ด์ง€: {0}/{1} | ์ด ๊ธธ์ด: {2}", - "inboxFull": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! {0}๋‹˜์˜ ๋ฐ›์€ ํŽธ์ง€ํ•จ์ด ๊ฐ€๋“ ์ฐผ์Šต๋‹ˆ๋‹ค.", - "inboxNoMsg": "๋ฐ›์€ ํŽธ์ง€ํ•จ์— ๋ฉ”์‹œ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", - "invitationSent": "{0}๋‹˜์—๊ฒŒ ์ดˆ๋Œ€์žฅ์„ ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค.", - "notInChannel": "{0}, ์Œ์„ฑ ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด {1}์— ์žˆ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์Œ์„ฑ ์ฑ„๋„์— ์žˆ์ง€ ์•Š์œผ๋ฉด ๋‹ค์‹œ ์ฐธ์—ฌํ•˜์„ธ์š”!", - "noTrackPlaying": "ํ˜„์žฌ ์žฌ์ƒ์ค‘์ธ ๊ณก์ด ์—†์Šต๋‹ˆ๋‹ค.", - "noTrackFound": "ํ•ด๋‹น ์ฟผ๋ฆฌ๋กœ ๊ณก์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์œ ํšจํ•œ URL์„ ์ œ๊ณตํ•ด์ฃผ์„ธ์š”.", - "noLinkSupport": "๊ฒ€์ƒ‰ ๋ช…๋ น์€ ๋งํฌ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค!", - "voted": "ํˆฌํ‘œํ•˜์…จ์Šต๋‹ˆ๋‹ค!", - "missingPosPerm": "DJ ๋˜๋Š” ๊ด€๋ฆฌ์ž๋งŒ ์œ„์น˜๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", - "missingModePerm": "DJ ๋˜๋Š” ๊ด€๋ฆฌ์ž๋งŒ ๋ฃจํ”„ ๋ชจ๋“œ๋ฅผ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", - "missingQueuePerm": "DJ ๋˜๋Š” ๊ด€๋ฆฌ์ž๋งŒ ๋Œ€๊ธฐ์—ด์—์„œ ๊ณก์„ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", - "missingAutoPlayPerm": "DJ ๋˜๋Š” ๊ด€๋ฆฌ์ž๋งŒ ์ž๋™์žฌ์ƒ ๋ชจ๋“œ๋ฅผ ํ™œ์„ฑํ™”ํ•˜๊ฑฐ๋‚˜ ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!", - "missingFunctionPerm": "DJ ๋˜๋Š” ๊ด€๋ฆฌ์ž๋งŒ์ด ์ด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", - "timeFormatError": "์ž˜๋ชป๋œ ์‹œ๊ฐ„ ํ˜•์‹์ž…๋‹ˆ๋‹ค. ์˜ˆ: 2:42 ๋˜๋Š” 12:39:31", - "lyricsNotFound": "๊ฐ€์‚ฌ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ฐ€์‚ฌ๋ฅผ ์ฐพ์œผ๋ ค๋ฉด /lyrics <๋…ธ๋ž˜ ์ œ๋ชฉ> <์ž‘๊ณก๊ฐ€>๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", - "missingTrackInfo": "์ผ๋ถ€ ํŠธ๋ž™ ์ •๋ณด๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "noVoiceChannel": "์Œ์„ฑ ์ฑ„๋„์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!", - "playlistAddError": "์ŠคํŠธ๋ฆฌ๋ฐ ๋น„๋””์˜ค๋ฅผ ์žฌ์ƒ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!", - "playlistAddError2": "์žฌ์ƒ๋ชฉ๋ก์— ๊ณก์„ ์ถ”๊ฐ€ํ•˜๋Š” ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค!", - "playlistLimited": "ํ•œ ์žฌ์ƒ๋ชฉ๋ก์— ์ตœ๋Œ€ {0}๊ณก๊นŒ์ง€ ์ถ”๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์ด์ œ ํ•œ๋„์— ๋„๋‹ฌํ–ˆ์Šต๋‹ˆ๋‹ค!", - "playlistRepeated": "์žฌ์ƒ๋ชฉ๋ก์— ์ด๋ฏธ ๊ฐ™์€ ๊ณก์ด ์žˆ์Šต๋‹ˆ๋‹ค!", - "playlistAdded": "โค๏ธ **{0}**์„(๋ฅผ) {1}์˜ ์žฌ์ƒ๋ชฉ๋ก [`{2}`] ์— ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค!", - "playerDropdown": "๊ฑด๋„ˆ๋›ฐ๊ธฐ ํ•  ๋…ธ๋ž˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”...", - "playerFilter": "์ ์šฉํ•  ํ•„ํ„ฐ๋ฅผ ์„ ํƒํ•˜์„ธ์š”...", - "buttonBack": "์ด์ „", - "buttonPause": "์ผ์‹œ์ •์ง€", - "buttonResume": "์žฌ๊ฐœ", - "buttonSkip": "์Šคํ‚ต", - "buttonLeave": "๋‚˜๊ฐ€๊ธฐ", - "buttonLoop": "๋ฐ˜๋ณต", - "buttonVolumeUp": "๋ณผ๋ฅจ ๋†’์ด๊ธฐ", - "buttonVolumeDown": "๋ณผ๋ฅจ ๋‚ฎ์ถ”๊ธฐ", - "buttonVolumeMute": "์Œ์†Œ๊ฑฐ", - "buttonVolumeUnmute": "์Œ์†Œ๊ฑฐ ํ•ด์ œ", - "buttonAutoPlay": "์ž๋™์žฌ์ƒ", - "buttonShuffle": "์…”ํ”Œ", - "buttonForward": "์•ž์œผ๋กœ", - "buttonRewind": "๋’ค๋กœ", - "buttonLyrics": "๊ฐ€์‚ฌ", - "nowplayingDesc": "**ํ˜„์žฌ ์žฌ์ƒ์ค‘์ธ ๊ณก:**\n```{0}```", - "nowplayingField": "๋‹ค์Œ ๊ณก:", - "nowplayingLink": "{0}์—์„œ ๋“ฃ๊ธฐ", - "connect": "{0}์— ์—ฐ๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "live": "๋ผ์ด๋ธŒ", - "playlistLoad": "์žฌ์ƒ๋ชฉ๋ก **{0}**์„(๋ฅผ) ๋Œ€๊ธฐ์—ด์— `{1}`๊ฐœ์˜ ๊ณก๊ณผ ํ•จ๊ป˜ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.", - "trackLoad": "**{2}**์˜ **[{0}](<{1}>)** (`{3}`)๋ฅผ ์žฌ์ƒ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ•˜๊ณ  ์žฌ์ƒ์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.\n", - "trackLoad_pos": "**{2}**์˜ **[{0}](<{1}>)** (`{3}`)๋ฅผ ๋Œ€๊ธฐ์—ด์˜ **{4}**๋ฒˆ์งธ๋กœ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.\n", - "searchTitle": "๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ: {0}", - "searchDesc": "โžฅ ํ”Œ๋žซํผ: {0} **{1}**\nโžฅ ๊ฒฐ๊ณผ: **{2}**\n\n{3}", - "searchWait": "๋Œ€๊ธฐ์—ด์— ์ถ”๊ฐ€ํ•  ๊ณก์„ ์„ ํƒํ•˜์„ธ์š”.", - "searchTimeout": "๊ฒ€์ƒ‰์ด ์‹œ๊ฐ„ ์ดˆ๊ณผ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‚˜์ค‘์— ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.", - "searchSuccess": "๊ณก์ด ๋Œ€๊ธฐ์—ด์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "queueTitle": "๋‹ค์Œ ๋Œ€๊ธฐ์—ด:", - "historyTitle": "์ด์ „ ๊ณก ๋Œ€๊ธฐ์—ด:", - "viewTitle": "์Œ์•… ๋Œ€๊ธฐ์—ด", - "viewDesc": "**ํ˜„์žฌ ์žฌ์ƒ์ค‘: [์—ฌ๊ธฐ๋ฅผ ํด๋ฆญํ•˜์—ฌ ๋“ฃ๊ธฐ]({0}) โฎฏ**\n{1}", - "viewFooter": "ํŽ˜์ด์ง€: {0}/{1} | ์ด ์žฌ์ƒ ์‹œ๊ฐ„: {2}", - "pauseError": "ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์ด๋ฏธ ์ผ์‹œ์ •์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "pauseVote": "{0}๋‹˜์ด ๋…ธ๋ž˜ ์ผ์‹œ์ •์ง€ ํˆฌํ‘œ๋ฅผ ํ–ˆ์Šต๋‹ˆ๋‹ค. [{1}/{2}]", - "paused": "{0}๋‹˜์ด ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์ผ์‹œ์ •์ง€ํ–ˆ์Šต๋‹ˆ๋‹ค.", - "resumeError": "ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์ผ์‹œ์ •์ง€๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.", - "resumeVote": "{0}๋‹˜์ด ๋…ธ๋ž˜ ์žฌ์ƒ ํˆฌํ‘œ๋ฅผ ํ–ˆ์Šต๋‹ˆ๋‹ค. [{1}/{2}]", - "resumed": "{0}๋‹˜์ด ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์žฌ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", - "shuffleError": "์…”ํ”Œํ•˜๊ธฐ ์ „์— ๋” ๋งŽ์€ ๋…ธ๋ž˜๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”.", - "shuffleVote": "{0}๋‹˜์ด ํ”Œ๋ ˆ์ด์–ด ์…”ํ”Œ ํˆฌํ‘œ๋ฅผ ํ–ˆ์Šต๋‹ˆ๋‹ค. [{1}/{2}]", - "shuffled": "ํ”Œ๋ ˆ์ด์–ด ํ๊ฐ€ ์…”ํ”Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "skipError": "๊ฑด๋„ˆ๋›ธ ๋…ธ๋ž˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", - "skipVote": "{0}๋‹˜์ด ๋…ธ๋ž˜ ๊ฑด๋„ˆ๋›ฐ๊ธฐ ํˆฌํ‘œ๋ฅผ ํ–ˆ์Šต๋‹ˆ๋‹ค. [{1}/{2}]", - "skipped": "{0}๋‹˜์ด ๋…ธ๋ž˜๋ฅผ ๊ฑด๋„ˆ๋›ฐ์—ˆ์Šต๋‹ˆ๋‹ค.", - "backVote": "{0}๋‹˜์ด ์ด์ „ ๋…ธ๋ž˜๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ ํˆฌํ‘œ๋ฅผ ํ–ˆ์Šต๋‹ˆ๋‹ค. [{1}/{2}]", - "backed": "`{0}`๋‹˜์ด ์ด์ „ ๋…ธ๋ž˜๋กœ ๊ฑด๋„ˆ๋›ฐ์—ˆ์Šต๋‹ˆ๋‹ค.", - "leaveVote": "{0}๋‹˜์ด ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์ค‘์ง€ํ•˜๊ธฐ ํˆฌํ‘œ๋ฅผ ํ–ˆ์Šต๋‹ˆ๋‹ค. [{1}/{2}]", - "left": "`{0}`๋‹˜์ด ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์ค‘์ง€ํ–ˆ์Šต๋‹ˆ๋‹ค.", - "seek": "ํ”Œ๋ ˆ์ด์–ด๊ฐ€ **{0}**๋กœ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "repeat": "๋ฐ˜๋ณต ๋ชจ๋“œ๊ฐ€ `{0}`(์œผ)๋กœ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "cleared": " `{0}`์˜ ๋ชจ๋“  ํŠธ๋ž™์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "removed": " `{0}`๊ฐœ์˜ ํŠธ๋ž™์ด ํ์—์„œ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "forward": "ํ”Œ๋ ˆ์ด์–ด๊ฐ€ **{0}**๋กœ ์ „์ง„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "rewind": "ํ”Œ๋ ˆ์ด์–ด๊ฐ€ **{0}**๋กœ ๋˜๊ฐ๊ธฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "replay": "ํ˜„์žฌ ๋…ธ๋ž˜๋ฅผ ๋‹ค์‹œ ์žฌ์ƒํ•ฉ๋‹ˆ๋‹ค.", - "swapped": "`{0}`์™€ `{1}`์ด(๊ฐ€) ์Šค์™‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "moved": "`{0}`์„(๋ฅผ) `{1}`๋กœ ์ด๋™ํ–ˆ์Šต๋‹ˆ๋‹ค.", - "autoplay": "์ž๋™ ์žฌ์ƒ ๋ชจ๋“œ๊ฐ€ **{0}**(์œผ)๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", - "notdj": "๋‹น์‹ ์€ DJ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ํ˜„์žฌ DJ๋Š” {0}์ž…๋‹ˆ๋‹ค.", - "djToMe": "์ž์‹ ์ด๋‚˜ ๋ด‡์—๊ฒŒ DJ๋ฅผ ์ด์ „ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", - "djNotInChannel": "`{0}`๋‹˜์ด ์Œ์„ฑ ์ฑ„๋„์— ์—†์Šต๋‹ˆ๋‹ค.", - "djswap": "๋‹น์‹ ์€ DJ ๊ถŒํ•œ์„ `{0}`๋‹˜์—๊ฒŒ ์ด์ „ํ–ˆ์Šต๋‹ˆ๋‹ค.", - "voicelinkQueueFull": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ํ์— `{0}`๊ฐœ์˜ ํŠธ๋ž™์„ ๋ชจ๋‘ ์ถ”๊ฐ€ํ•˜์…จ์Šต๋‹ˆ๋‹ค!", - "voicelinkOutofList": "์œ ํšจํ•œ ํŠธ๋ž™ ์ธ๋ฑ์Šค๋ฅผ ์ œ๊ณตํ•ด์ฃผ์„ธ์š”!", - "voicelinkDuplicateTrack": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ์ด ํŠธ๋ž™์€ ์ด๋ฏธ ํ์— ์žˆ์Šต๋‹ˆ๋‹ค.", - "decodeError": "ํŒŒ์ผ ๋””์ฝ”๋”ฉ ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค!", - "invalidStartTime": "ํšจ๋ ฅ ์—†๋Š” ์‹œ์ž‘ ์‹œ๊ฐ„! ์‹œ๊ฐ„์€ `00:00` ๊ณผ `{0}` ์‚ฌ์ด์— ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", - "invalidEndTime": "ํšจ๋ ฅ ์—†๋Š” ์ข…๋ฃŒ ์‹œ๊ฐ„! ์‹œ๊ฐ„์€ `00:00` ๊ณผ `{0}` ์‚ฌ์ด์— ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", - "invalidTimeOrder": "์ข…๋ฃŒ ์‹œ๊ฐ„์€ ์‹œ์ž‘ ์‹œ๊ฐ„๋ณด๋‹ค ํด์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", - "setStageAnnounceTemplate": "์™„๋ฃŒ! ์ด์ œ๋ถ€ํ„ฐ ์ง€๊ธˆ ์žˆ๋Š” ์Œ์„ฑ ์ƒํƒœ๋Š” ๊ท€ํ•˜์˜ ํ…œํ”Œ๋ฆฟ์— ๋”ฐ๋ผ ์ด๋ฆ„์ด ์ง€์ •๋ฉ๋‹ˆ๋‹ค. ๋ช‡ ์ดˆ ํ›„์— ์—…๋ฐ์ดํŠธ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.", - "createSongRequestChannel": "๋…ธ๋ž˜ ์š”์ฒญ ์ฑ„๋„ ({0})์ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ํ•ด๋‹น ์ฑ„๋„์—์„œ ๋…ธ๋ž˜ ์ œ๋ชฉ์ด๋‚˜ URL๋กœ ์›ํ•˜๋Š” ๋…ธ๋ž˜๋ฅผ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ด‡ ์ ‘๋‘์‚ฌ๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค." -} \ No newline at end of file + "common": { + "errors": { + "unknown": "โš ๏ธ ๋ช…๋ น์–ด ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค! ๋‚˜์ค‘์— ๋‹ค์‹œ ์‹œ๋„ํ•˜๊ฑฐ๋‚˜ ์ถ”๊ฐ€ ์ง€์›์„ ์œ„ํ•ด ๋””์Šค์ฝ”๋“œ ์„œ๋ฒ„์— ์ฐธ์—ฌํ•˜์‹ญ์‹œ์˜ค.", + "missingIntents": "์ฃ„์†กํ•˜์ง€๋งŒ ์ด ๋ช…๋ น์„ ์‹คํ–‰ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ด‡์— ํ•„์š”ํ•œ ์š”์ฒญ ์˜๋„๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค:`({0})`." + }, + "status": { + "enabled": "ํ™œ์„ฑํ™”๋จ", + "disabled": "๋น„ํ™œ์„ฑํ™”๋จ", + "live": "๋ผ์ด๋ธŒ" + } + }, + "player": { + "errors": { + "nodeReconnect": "๋‹ค์‹œ ์‹œ๋„ํ•˜์„ธ์š”! ๋…ธ๋“œ๊ฐ€ ๋‹ค์‹œ ์—ฐ๊ฒฐ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์ฃผ์„ธ์š”.", + "noPlaySource": "์žฌ์ƒ ๊ฐ€๋Šฅํ•œ ์†Œ์Šค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!", + "noPlayer": "์ด ์„œ๋ฒ„์—์„œ ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + "noTrackPlaying": "ํ˜„์žฌ ์žฌ์ƒ์ค‘์ธ ๊ณก์ด ์—†์Šต๋‹ˆ๋‹ค.", + "noTrackFound": "ํ•ด๋‹น ์ฟผ๋ฆฌ๋กœ ๊ณก์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์œ ํšจํ•œ URL์„ ์ œ๊ณตํ•ด์ฃผ์„ธ์š”." + }, + "dropdown": { + "trackSelect": "๊ฑด๋„ˆ๋›ฐ๊ธฐ ํ•  ๋…ธ๋ž˜๋ฅผ ์„ ํƒํ•˜์„ธ์š”...", + "filterSelect": "์ ์šฉํ•  ํ•„ํ„ฐ๋ฅผ ์„ ํƒํ•˜์„ธ์š”..." + }, + "buttons": { + "back": "์ด์ „", + "pause": "์ผ์‹œ์ •์ง€", + "resume": "์žฌ๊ฐœ", + "skip": "์Šคํ‚ต", + "leave": "๋‚˜๊ฐ€๊ธฐ", + "loop": "๋ฐ˜๋ณต", + "volumeUp": "๋ณผ๋ฅจ ๋†’์ด๊ธฐ", + "volumeDown": "๋ณผ๋ฅจ ๋‚ฎ์ถ”๊ธฐ", + "volumeMute": "์Œ์†Œ๊ฑฐ", + "volumeUnmute": "์Œ์†Œ๊ฑฐ ํ•ด์ œ", + "autoPlay": "์ž๋™์žฌ์ƒ", + "shuffle": "์…”ํ”Œ", + "forward": "์•ž์œผ๋กœ", + "rewind": "๋’ค๋กœ", + "lyrics": "๊ฐ€์‚ฌ" + }, + "playback": { + "nowplayingDesc": "**ํ˜„์žฌ ์žฌ์ƒ์ค‘์ธ ๊ณก:**\n```{0}```", + "nowplayingField": "๋‹ค์Œ ๊ณก:", + "nowplayingLink": "{0}์—์„œ ๋“ฃ๊ธฐ", + "playlistLoad": "์žฌ์ƒ๋ชฉ๋ก **{0}**์„(๋ฅผ) ๋Œ€๊ธฐ์—ด์— `{1}`๊ฐœ์˜ ๊ณก๊ณผ ํ•จ๊ป˜ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.", + "trackLoad": "**{2}**์˜ **[{0}](<{1}>)** (`{3}`)๋ฅผ ์žฌ์ƒ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ•˜๊ณ  ์žฌ์ƒ์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.\n", + "trackLoadPos": "**{2}**์˜ **[{0}](<{1}>)** (`{3}`)๋ฅผ ๋Œ€๊ธฐ์—ด์˜ **{4}**๋ฒˆ์งธ๋กœ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.\n" + }, + "controls": { + "pause": { + "error": "ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์ด๋ฏธ ์ผ์‹œ์ •์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "vote": "{0}๋‹˜์ด ๋…ธ๋ž˜ ์ผ์‹œ์ •์ง€ ํˆฌํ‘œ๋ฅผ ํ–ˆ์Šต๋‹ˆ๋‹ค. [{1}/{2}]", + "success": "{0}๋‹˜์ด ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์ผ์‹œ์ •์ง€ํ–ˆ์Šต๋‹ˆ๋‹ค." + }, + "resume": { + "error": "ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์ผ์‹œ์ •์ง€๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.", + "vote": "{0}๋‹˜์ด ๋…ธ๋ž˜ ์žฌ์ƒ ํˆฌํ‘œ๋ฅผ ํ–ˆ์Šต๋‹ˆ๋‹ค. [{1}/{2}]", + "success": "{0}๋‹˜์ด ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์žฌ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค." + }, + "shuffle": { + "error": "์…”ํ”Œํ•˜๊ธฐ ์ „์— ๋” ๋งŽ์€ ๋…ธ๋ž˜๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”.", + "vote": "{0}๋‹˜์ด ํ”Œ๋ ˆ์ด์–ด ์…”ํ”Œ ํˆฌํ‘œ๋ฅผ ํ–ˆ์Šต๋‹ˆ๋‹ค. [{1}/{2}]", + "success": "ํ”Œ๋ ˆ์ด์–ด ํ๊ฐ€ ์…”ํ”Œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + }, + "skip": { + "error": "๊ฑด๋„ˆ๋›ธ ๋…ธ๋ž˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", + "vote": "{0}๋‹˜์ด ๋…ธ๋ž˜ ๊ฑด๋„ˆ๋›ฐ๊ธฐ ํˆฌํ‘œ๋ฅผ ํ–ˆ์Šต๋‹ˆ๋‹ค. [{1}/{2}]", + "success": "{0}๋‹˜์ด ๋…ธ๋ž˜๋ฅผ ๊ฑด๋„ˆ๋›ฐ์—ˆ์Šต๋‹ˆ๋‹ค." + }, + "back": { + "vote": "{0}๋‹˜์ด ์ด์ „ ๋…ธ๋ž˜๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ ํˆฌํ‘œ๋ฅผ ํ–ˆ์Šต๋‹ˆ๋‹ค. [{1}/{2}]", + "success": "`{0}`๋‹˜์ด ์ด์ „ ๋…ธ๋ž˜๋กœ ๊ฑด๋„ˆ๋›ฐ์—ˆ์Šต๋‹ˆ๋‹ค." + }, + "leave": { + "vote": "{0}๋‹˜์ด ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์ค‘์ง€ํ•˜๊ธฐ ํˆฌํ‘œ๋ฅผ ํ–ˆ์Šต๋‹ˆ๋‹ค. [{1}/{2}]", + "success": "`{0}`๋‹˜์ด ํ”Œ๋ ˆ์ด์–ด๋ฅผ ์ค‘์ง€ํ–ˆ์Šต๋‹ˆ๋‹ค." + }, + "seek": "ํ”Œ๋ ˆ์ด์–ด๊ฐ€ **{0}**๋กœ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "repeat": "๋ฐ˜๋ณต ๋ชจ๋“œ๊ฐ€ `{0}`(์œผ)๋กœ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "forward": "ํ”Œ๋ ˆ์ด์–ด๊ฐ€ **{0}**๋กœ ์ „์ง„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "rewind": "ํ”Œ๋ ˆ์ด์–ด๊ฐ€ **{0}**๋กœ ๋˜๊ฐ๊ธฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "replay": "ํ˜„์žฌ ๋…ธ๋ž˜๋ฅผ ๋‹ค์‹œ ์žฌ์ƒํ•ฉ๋‹ˆ๋‹ค.", + "autoplay": "์ž๋™ ์žฌ์ƒ ๋ชจ๋“œ๊ฐ€ **{0}**(์œผ)๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + } + }, + "voice": { + "connection": { + "noChannel": "์—ฐ๊ฒฐํ•  ์Œ์„ฑ ์ฑ„๋„์ด ์—†์Šต๋‹ˆ๋‹ค. ํ•˜๋‚˜๋ฅผ ์ œ๊ณตํ•˜๊ฑฐ๋‚˜ ์ฐธ์—ฌํ•˜์‹ญ์‹œ์˜ค.", + "alreadyConnected": "์ด๋ฏธ ์Œ์„ฑ ์ฑ„๋„์— ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.", + "noPermission": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์Œ์„ฑ ์ฑ„๋„์— ์ฐธ์—ฌํ•˜๊ฑฐ๋‚˜ ๋งํ•  ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.", + "notInChannel": "{0}, ์Œ์„ฑ ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด {1}์— ์žˆ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์Œ์„ฑ ์ฑ„๋„์— ์žˆ์ง€ ์•Š์œผ๋ฉด ๋‹ค์‹œ ์ฐธ์—ฌํ•˜์„ธ์š”!", + "connect": "{0}์— ์—ฐ๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + }, + "stageChannel": { + "setAnnounceTemplate": "์™„๋ฃŒ! ์ด์ œ๋ถ€ํ„ฐ ์ง€๊ธˆ ์žˆ๋Š” ์Œ์„ฑ ์ƒํƒœ๋Š” ๊ท€ํ•˜์˜ ํ…œํ”Œ๋ฆฟ์— ๋”ฐ๋ผ ์ด๋ฆ„์ด ์ง€์ •๋ฉ๋‹ˆ๋‹ค. ๋ช‡ ์ดˆ ํ›„์— ์—…๋ฐ์ดํŠธ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค." + } + }, + "permissions": { + "noCreatePermission": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ๋…ธ๋ž˜ ์š”์ฒญ ์ฑ„๋„์„ ์ƒ์„ฑํ•  ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.", + "missingPosition": "DJ ๋˜๋Š” ๊ด€๋ฆฌ์ž๋งŒ ์œ„์น˜๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "missingMode": "DJ ๋˜๋Š” ๊ด€๋ฆฌ์ž๋งŒ ๋ฃจํ”„ ๋ชจ๋“œ๋ฅผ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "missingQueue": "DJ ๋˜๋Š” ๊ด€๋ฆฌ์ž๋งŒ ๋Œ€๊ธฐ์—ด์—์„œ ๊ณก์„ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "missingAutoPlay": "DJ ๋˜๋Š” ๊ด€๋ฆฌ์ž๋งŒ ์ž๋™์žฌ์ƒ ๋ชจ๋“œ๋ฅผ ํ™œ์„ฑํ™”ํ•˜๊ฑฐ๋‚˜ ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!", + "missingFunction": "DJ ๋˜๋Š” ๊ด€๋ฆฌ์ž๋งŒ์ด ์ด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "notDj": "๋‹น์‹ ์€ DJ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ํ˜„์žฌ DJ๋Š” {0}์ž…๋‹ˆ๋‹ค.", + "djToSelf": "์ž์‹ ์ด๋‚˜ ๋ด‡์—๊ฒŒ DJ๋ฅผ ์ด์ „ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + "djNotInChannel": "`{0}`๋‹˜์ด ์Œ์„ฑ ์ฑ„๋„์— ์—†์Šต๋‹ˆ๋‹ค.", + "djSwapped": "๋‹น์‹ ์€ DJ ๊ถŒํ•œ์„ `{0}`๋‹˜์—๊ฒŒ ์ด์ „ํ–ˆ์Šต๋‹ˆ๋‹ค." + }, + "settings": { + "actions": { + "languageNotFound": "์–ธ์–ด ํŒฉ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค! ๊ธฐ์กด ์–ธ์–ด ํŒฉ์„ ์„ ํƒํ•˜์‹ญ์‹œ์˜ค.", + "languageChanged": "์„ฑ๊ณต์ ์œผ๋กœ `{0}` ์–ธ์–ด ํŒฉ์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "prefixSet": "์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ์ด ์„œ๋ฒ„์—์„œ ๋‚ด ์ ‘๋‘์‚ฌ๋Š” ์ด์ œ `{0}`์ž…๋‹ˆ๋‹ค. `{1}ping`์„ ์‹คํ–‰ํ•˜์—ฌ ํ…Œ์ŠคํŠธํ•ด๋ณด์„ธ์š”.", + "djSet": "DJ๋ฅผ {0}(์œผ)๋กœ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.", + "queueModeSet": "ํ ๋ชจ๋“œ๋ฅผ `{0}`(์œผ)๋กœ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.", + "mode247": "์ด์ œ 24/7 ๋ชจ๋“œ์—์„œ `{0}`์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "bypassVote": "์ด์ œ `{0}` ํˆฌํ‘œ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", + "volumeSet": "๋ณผ๋ฅจ์„ `{0}`%๋กœ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.", + "controllerToggled": "์ด์ œ ์Œ์•… ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ `{0}`(์œผ)๋กœ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "duplicateTrackToggled": "์ด์ œ ๋Œ€๊ธฐ์—ด์—์„œ ์ค‘๋ณต๋œ ํŠธ๋ž™์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด `{0}`(์œผ)๋กœ ์„ค์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "controllerMsgToggled": "์ด์ œ ์Œ์•… ์ปจํŠธ๋กค๋Ÿฌ๋กœ๋ถ€ํ„ฐ `{0}` ๋ฉ”์‹œ์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.", + "silentMsgToggled": "๋‹น์‹ ์€ {0} ์นจ๋ฌต ๋ฉ”์‹œ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค." + }, + "menu": "์„œ๋ฒ„ ์„ค์ • | {0}", + "basic": { + "title": "โค๏ธ ๊ธฐ๋ณธ ์ •๋ณด:", + "value": "```์ ‘๋‘์‚ฌ: {0}\n์–ธ์–ด: {1}\n์Œ์•… ์ปจํŠธ๋กค๋Ÿฌ ์‚ฌ์šฉ: {2}\nDJ ์—ญํ• : @{3}\nํˆฌํ‘œ ์šฐํšŒ: {4}\n24/7 ๋ชจ๋“œ: {5}\n๊ธฐ๋ณธ ๋ณผ๋ฅจ: {6}%\n์žฌ์ƒ ์‹œ๊ฐ„: {7}```" + }, + "queue": { + "title": "๐Ÿ”— ๋Œ€๊ธฐ์—ด ์ •๋ณด:", + "value": "```ํ ๋ชจ๋“œ: {0}\n์ตœ๋Œ€ ๋…ธ๋ž˜: {1}\n์ค‘๋ณต๋œ ํŠธ๋ž™ ํ—ˆ์šฉ: {2}```" + }, + "voice": { + "title": "๐ŸŽค ์Œ์„ฑ ์ƒํƒœ ์ •๋ณด:" + }, + "permissions": { + "title": "โœจ ๊ถŒํ•œ:", + "value": "```{0} ๊ด€๋ฆฌ์ž\n{1} ์„œ๋ฒ„ ๊ด€๋ฆฌ\n{2} ์ฑ„๋„ ๊ด€๋ฆฌ\n{3} ๋ฉ”์‹œ์ง€ ๊ด€๋ฆฌ```" + } + }, + "ping": { + "title1": "๋ด‡ ์ •๋ณด:", + "title2": "ํ”Œ๋ ˆ์ด์–ด ์ •๋ณด:", + "field1": "```์‰ฌ๋“œ ID: {0}/{1}\n์‰ฌ๋“œ ๋Œ€๊ธฐ ์‹œ๊ฐ„: {2:.3f}s {3}\n์ง€์—ญ: {4}```", + "field2": "```๋…ธ๋“œ: {0} - {1:.3f}s\nํ”Œ๋ ˆ์ด์–ด: {2}\n์Œ์„ฑ ์ง€์—ญ: {3}```" + }, + "effects": { + "added": "`{0}` ํ•„ํ„ฐ๋ฅผ ์ ์šฉํ•˜์„ธ์š”.", + "cleared": "ํšจ๊ณผ๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!", + "tagInUse": "์ด ํ•„ํ„ฐ๋Š” ์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๋‹ค! ์‚ญ์ œํ•˜๋ ค๋ฉด /cleareffect ๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค." + }, + "playlist": { + "view": { + "title": "{0}์˜ ๋ชจ๋“  ์žฌ์ƒ ๋ชฉ๋ก", + "headers": "ID:,์‹œ๊ฐ„:,์ด๋ฆ„:,ํŠธ๋ž™:", + "footer": "/playlist play [์žฌ์ƒ ๋ชฉ๋ก]์„ ์ž…๋ ฅํ•˜์—ฌ ์žฌ์ƒ ๋ชฉ๋ก์„ ๋Œ€๊ธฐ์—ด์— ์ถ”๊ฐ€ํ•˜์„ธ์š”.", + "detailTitle": "์žฌ์ƒ ๋ชฉ๋ก ๋ทฐ์–ด", + "detailDesc": "```์ด๋ฆ„: {0} [{1}]\n์ด ํŠธ๋ž™: {2}\n์†Œ์œ ์ž: {3}\n์œ ํ˜•: {4}\n```", + "permsValue": "๐Ÿ“– ์ฝ๊ธฐ: โœ“ โœ๐Ÿฝ ์“ฐ๊ธฐ: {0} ๐Ÿ—‘๏ธ ์‚ญ์ œ: {1}", + "permsValue2": "๐Ÿ“– ์ฝ๊ธฐ: {0}", + "trackList": "ํŠธ๋ž™", + "footer2": "์ด ๊ธธ์ด: {0}", + "playlistSelect": "๋ณด๊ธฐ ์œ„ํ•ด ์žฌ์ƒ ๋ชฉ๋ก์„ ์„ ํƒํ•˜์„ธ์š”..." + }, + "errors": { + "notFound": "์žฌ์ƒ ๋ชฉ๋ก [{0}]์„(๋ฅผ) ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์žฌ์ƒ ๋ชฉ๋ก์„ ๋ณด๋ ค๋ฉด /playlist view๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", + "noAccess": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์ด ์žฌ์ƒ ๋ชฉ๋ก์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!", + "noTrack": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์žฌ์ƒ ๋ชฉ๋ก [{0}]์— ํŠธ๋ž™์ด ์—†์Šต๋‹ˆ๋‹ค.", + "notAllowed": "์ด ๋ช…๋ น์–ด๋Š” ์—ฐ๊ฒฐ๋œ ๋ฐ ๊ณต์œ ๋œ ์žฌ์ƒ ๋ชฉ๋ก์—์„œ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", + "nameOverLimit": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์žฌ์ƒ ๋ชฉ๋ก์˜ ์ด๋ฆ„์€ 10์ž๋ฅผ ์ดˆ๊ณผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + "sameName": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์ด ์ด๋ฆ„์€ ์ƒˆ ์ด๋ฆ„๊ณผ ๋™์ผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + "deleteDefault": "๊ธฐ๋ณธ ์žฌ์ƒ ๋ชฉ๋ก์€ ์‚ญ์ œํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + "limitReached": "๋” ์ด์ƒ {0}๊ฐœ ์ด์ƒ์˜ ์žฌ์ƒ ๋ชฉ๋ก์„ ๋งŒ๋“ค ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!", + "exists": "์žฌ์ƒ ๋ชฉ๋ก [{0}]์ด(๊ฐ€) ์ด๋ฏธ ์žˆ์Šต๋‹ˆ๋‹ค.", + "invalidUrl": "์œ ํšจํ•œ ๋งํฌ ๋˜๋Š” ๊ณต๊ฐœ Spotify ๋˜๋Š” YouTube ์žฌ์ƒ ๋ชฉ๋ก ๋งํฌ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", + "trackLimitReached": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์žฌ์ƒ ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š” ๋…ธ๋ž˜๋Š” {0}๊ณก๊นŒ์ง€์ž…๋‹ˆ๋‹ค.", + "playlistLinkNotAllowed": "์žฌ์ƒ ๋ชฉ๋ก ๋งํฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + "streamNotAllowed": "์ŠคํŠธ๋ฆฌ๋ฐ ๋น„๋””์˜ค๋ฅผ ์žฌ์ƒ ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + "positionNotFound": "์žฌ์ƒ ๋ชฉ๋ก [{1}]์—์„œ ์œ„์น˜ {0}์„(๋ฅผ) ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!", + "streaming": "์ŠคํŠธ๋ฆฌ๋ฐ ๋น„๋””์˜ค๋ฅผ ์žฌ์ƒ๋ชฉ๋ก์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค!", + "track": "์žฌ์ƒ๋ชฉ๋ก์— ๊ณก์„ ์ถ”๊ฐ€ํ•˜๋Š” ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค!", + "trackLimited": "ํ•œ ์žฌ์ƒ๋ชฉ๋ก์— ์ตœ๋Œ€ {0}๊ณก๊นŒ์ง€ ์ถ”๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์ด์ œ ํ•œ๋„์— ๋„๋‹ฌํ–ˆ์Šต๋‹ˆ๋‹ค!", + "trackRepeated": "์žฌ์ƒ๋ชฉ๋ก์— ์ด๋ฏธ ๊ฐ™์€ ๊ณก์ด ์žˆ์Šต๋‹ˆ๋‹ค!" + }, + "actions": { + "play": "์žฌ์ƒ ๋ชฉ๋ก [{0}]์„(๋ฅผ) ๋Œ€๊ธฐ์—ด์— {1}๊ณก ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.", + "removed": "์žฌ์ƒ ๋ชฉ๋ก [{0}]์„(๋ฅผ) ์‚ญ์ œํ–ˆ์Šต๋‹ˆ๋‹ค.", + "created": "{0} ์žฌ์ƒ ๋ชฉ๋ก์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ /playlist view๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", + "renamed": "{0}์„(๋ฅผ) {1}(์œผ)๋กœ ์ด๋ฆ„์„ ๋ฐ”๊ฟจ์Šต๋‹ˆ๋‹ค.", + "trackRemoved": "๐Ÿ‘‹ {1}๋‹˜์˜ ์žฌ์ƒ ๋ชฉ๋ก [{2}]์—์„œ **{0}**์„(๋ฅผ) ์ œ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค.", + "cleared": "์žฌ์ƒ ๋ชฉ๋ก [{0}]์„(๋ฅผ) ์„ฑ๊ณต์ ์œผ๋กœ ์ง€์› ์Šต๋‹ˆ๋‹ค.", + "trackAdded": "โค๏ธ **{0}**์„(๋ฅผ) {1}์˜ ์žฌ์ƒ๋ชฉ๋ก [`{2}`] ์— ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค!" + }, + "sharing": { + "sendErrorPlayer": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์ž์‹ ์—๊ฒŒ ์ดˆ๋Œ€์žฅ์„ ๋ณด๋‚ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + "sendErrorBot": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ๋ด‡์—๊ฒŒ ์ดˆ๋Œ€์žฅ์„ ๋ณด๋‚ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", + "belongs": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์ด ์žฌ์ƒ ๋ชฉ๋ก์€ <@{0}>๋‹˜์˜ ๊ฒƒ์ž…๋‹ˆ๋‹ค.", + "alreadyShared": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์ด ์žฌ์ƒ ๋ชฉ๋ก์€ {0}๊ณผ(์™€) ๊ณต์œ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "alreadySent": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! ์ด๋ฏธ ์ดˆ๋Œ€์žฅ์„ ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค.", + "noAccount": "{0}๋‹˜์€ ์žฌ์ƒ ๋ชฉ๋ก ๊ณ„์ •์„ ๋งŒ๋“ค์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.", + "invitationSent": "{0}๋‹˜์—๊ฒŒ ์ดˆ๋Œ€์žฅ์„ ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค." + }, + "inbox": { + "full": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค! {0}๋‹˜์˜ ๋ฐ›์€ ํŽธ์ง€ํ•จ์ด ๊ฐ€๋“ ์ฐผ์Šต๋‹ˆ๋‹ค.", + "noMessages": "๋ฐ›์€ ํŽธ์ง€ํ•จ์— ๋ฉ”์‹œ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค." + } + }, + "search": { + "noLinkSupport": "๊ฒ€์ƒ‰ ๋ช…๋ น์€ ๋งํฌ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค!", + "title": "๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ: {0}", + "desc": "โžฅ ํ”Œ๋žซํผ: {0} **{1}**\nโžฅ ๊ฒฐ๊ณผ: **{2}**\n\n{3}", + "wait": "๋Œ€๊ธฐ์—ด์— ์ถ”๊ฐ€ํ•  ๊ณก์„ ์„ ํƒํ•˜์„ธ์š”.", + "success": "๊ณก์ด ๋Œ€๊ธฐ์—ด์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค." + }, + "voting": { + "voted": "ํˆฌํ‘œํ•˜์…จ์Šต๋‹ˆ๋‹ค!" + }, + "time": { + "formatError": "์ž˜๋ชป๋œ ์‹œ๊ฐ„ ํ˜•์‹์ž…๋‹ˆ๋‹ค. ์˜ˆ: 2:42 ๋˜๋Š” 12:39:31", + "invalidStartTime": "ํšจ๋ ฅ ์—†๋Š” ์‹œ์ž‘ ์‹œ๊ฐ„! ์‹œ๊ฐ„์€ `00:00` ๊ณผ `{0}` ์‚ฌ์ด์— ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + "invalidEndTime": "ํšจ๋ ฅ ์—†๋Š” ์ข…๋ฃŒ ์‹œ๊ฐ„! ์‹œ๊ฐ„์€ `00:00` ๊ณผ `{0}` ์‚ฌ์ด์— ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค." + }, + "lyrics": { + "notFound": "๊ฐ€์‚ฌ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ฐ€์‚ฌ๋ฅผ ์ฐพ์œผ๋ ค๋ฉด /lyrics <๋…ธ๋ž˜ ์ œ๋ชฉ> <์ž‘๊ณก๊ฐ€>๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”." + }, + "queue": { + "management": { + "title": "๋‹ค์Œ ๋Œ€๊ธฐ์—ด:", + "cleared": " `{0}`์˜ ๋ชจ๋“  ํŠธ๋ž™์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "removed": " `{0}`๊ฐœ์˜ ํŠธ๋ž™์ด ํ์—์„œ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "swapped": "`{0}`์™€ `{1}`์ด(๊ฐ€) ์Šค์™‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "moved": "`{0}`์„(๋ฅผ) `{1}`๋กœ ์ด๋™ํ–ˆ์Šต๋‹ˆ๋‹ค." + }, + "view": { + "historyTitle": "์ด์ „ ๊ณก ๋Œ€๊ธฐ์—ด:", + "title": "์Œ์•… ๋Œ€๊ธฐ์—ด", + "desc": "**ํ˜„์žฌ ์žฌ์ƒ์ค‘: [์—ฌ๊ธฐ๋ฅผ ํด๋ฆญํ•˜์—ฌ ๋“ฃ๊ธฐ]({0}) โฎฏ**\n{1}" + }, + "errors": { + "queueFull": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ํ์— `{0}`๊ฐœ์˜ ํŠธ๋ž™์„ ๋ชจ๋‘ ์ถ”๊ฐ€ํ•˜์…จ์Šต๋‹ˆ๋‹ค!", + "outOfList": "์œ ํšจํ•œ ํŠธ๋ž™ ์ธ๋ฑ์Šค๋ฅผ ์ œ๊ณตํ•ด์ฃผ์„ธ์š”!", + "duplicateTrack": "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ์ด ํŠธ๋ž™์€ ์ด๋ฏธ ํ์— ์žˆ์Šต๋‹ˆ๋‹ค." + } + }, + "songRequest": { + "channelCreated": "๋…ธ๋ž˜ ์š”์ฒญ ์ฑ„๋„ ({0})์ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ํ•ด๋‹น ์ฑ„๋„์—์„œ ๋…ธ๋ž˜ ์ œ๋ชฉ์ด๋‚˜ URL๋กœ ์›ํ•˜๋Š” ๋…ธ๋ž˜๋ฅผ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ด‡ ์ ‘๋‘์‚ฌ๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค." + }, + "pagination": { + "prev": "์ด์ „", + "next": "๋‹ค์Œ", + "play": "์žฌ์ƒ", + "share": "๊ณต์œ ", + "export": "๋‚ด๋ณด๋‚ด๊ธฐ", + "delete": "์‚ญ์ œ", + "page": { + "title": "ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ ์ž…๋ ฅ", + "field": "ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ" + } + } +} diff --git a/langs/PL.json b/langs/PL.json index 1e0ff06..ac7fc50 100644 --- a/langs/PL.json +++ b/langs/PL.json @@ -1,172 +1,262 @@ { - "unknownException": "โš ๏ธ Coล› poszล‚o nie tak podczas wykonywania polecenia. Sprรณbuj ponownie pรณลบniej lub doล‚ฤ…ฤ‡z do serwera discord w celu uzyskania pomocy.", - "enabled": "wล‚ฤ…czony", - "disabled": "wyล‚ฤ…czony", - "nodeReconnect": "Sprรณbuj ponownie! Trwa ponowne ล‚ฤ…cznie z node'm.", - "noChannel": "Brak kanaล‚u gล‚osowego do doล‚ฤ…cznia. Doล‚ฤ…cz do jakiegoล›, lub wskaลผ kanaล‚ komendฤ… /connect", - "alreadyConnected": "Juลผ poล‚ฤ…czono z kanaล‚em gล‚osowym.", - "noPermission": "Niestety nie mam uprawnieล„, aby doล‚ฤ…czyฤ‡ do Twojego kanaล‚u gล‚osowego lub na nim mรณwiฤ‡.", - "noCreatePermission": "Niestety, nie mam uprawnieล„, aby stworzyฤ‡ kanaล‚ do ลผฤ…dania utworรณw.", - "noPlaySource": "Nie moลผna znaleลบฤ‡ ลผadnych odtwarzalnych ลบrรณdeล‚!", - "noPlayer": "Nie znaleziono ลผadnego odtwarzacza na tym serwerze.", - "notVote": "To polecenie wymaga gล‚osowania! Wpisz `/vote`, aby uzyskaฤ‡ wiฤ™cej informacji.", - "missingIntents": "Niestety to polecenie nie moลผe zostaฤ‡ wykonane, poniewaลผ bot nie posiada wymaganej intencji ลผฤ…dania: `({0})`.", - "languageNotFound": "Nie znaleziono pakietu jฤ™zykowego! wybierz istniejฤ…cy pakiet jฤ™zykowy.", - "changedLanguage": "Pomyล›lnie zmieniono pakiet jฤ™zykowy na: `{0}` .", - "setPrefix": "Gotowe! Mรณj prefix na tym serwerze to teraz `{0}`. Sprรณbuj uruchomiฤ‡ polecenie `{1}ping`, aby go przetestowaฤ‡.", - "setDJ": "Ustawiono DJ jako {0}.", - "setQueue": "Zmienionio tryb kolejki na `{0}`.", - "247": "Tryb 24/7: `{0}`", - "bypassVote": "System gล‚osowania: `{0}`", - "setVolume": "Ustawiono gล‚oล›noล›ฤ‡ na `{0}`%", - "toggleController": "Kontroler muzyki: `{0}`", - "toggleDuplicateTrack": "Unikanie duplikowania pozycji w kolejce: `{0}`", - "toggleControllerMsg": "Wiadomoล›ci od kontrolera muzyki: `{0}`", - "toggleSilentMsg": "Masz {0} ciche wiadomoล›ci.", - "settingsMenu": "Ustawnienia serwera | {0}", - "settingsTitle": "โค๏ธ Podstawowe informacje:", - "settingsValue": "```Prefix: {0}\nJฤ™zyk: {1}\nKontroler muzyki: {2}\nRola DJ: @{3}\nOminiฤ™cie gล‚osowania: {4}\n24/7: {5}\nDomyล›lna gล‚oล›noล›ฤ‡: {6}%\nCzas odtwarzania: {7}```", - "settingsTitle2": "๐Ÿ”— Informacje o kolejce:", - "settingsValue2": "```Tryb kolejki: {0}\nMaksymlna liczba utworรณw: {1}\nZduplikowane pozycje: {2}```", - "settingsTitle3": "๐ŸŽค Informacie w statusie kanaล‚u gล‚osowego:", - "settingsPermTitle": "โœจ Uprawnienia:", - "settingsPermValue": "```{0} Administrator\n{1} Zarzฤ…dzanie_serwerem\n{2} Zarzฤ…dzanie_kanaล‚em\n{3} Zarzฤ…dzanie_wiadomoล›ciami```", - "pingTitle1": "Informacje o bocie:", - "pingTitle2": "Infomracje o odtwarzaczu:", - "pingField1": "```ID Shard'a: {0}/{1}\nOpรณลบnienie shard'a: {2:.3f}s {3}\nRegion: {4}```", - "pingField2": "```Wฤ™zeล‚: {0} - {1:.3f}s\nOdtwarzacze: {2}\nRegion: {3}```", - "addEffect": "Naล‚oลผono efekt: `{0}`", - "clearEffect": "Efekty dลบwiฤ™kowe zostaล‚y wyczyszczone.", - "filterTagAlreadyInUse": "Ten efekt dลบwiฤ™kowy jest juลผ w uลผywany! Uลผyj /cleareffect aby go wyล‚ฤ…czyฤ‡.", - "playlistViewTitle": "๐Ÿ“œ Playlisty uลผytkownika {0}:", - "playlistViewHeaders": "ID:,Czas:,Nazwa:,Iloล›ฤ‡ pozycji:", - "playlistFooter": "Uลผyj /playlist play [nazwa_playlisty] by dodaฤ‡ playlistฤ™ do kolejki.", - "playlistNotFound": "Playlista [`{0}`] nie zostaล‚a znaleziona. Uลผyj /playlist view by zobaczyฤ‡ wszystkie swoje playlisty.", - "playlistNotAccess": "Przepraszamy! Nie masz uprawnieล„ do tej playlisty!", - "playlistNoTrack": "Bล‚ad! W playliล›cie [`{0}`] nie ma utworรณw.", - "playlistNotAllow": "Ta komenda jest niedozwolona w odniesienu do udostฤ™pnionych playlist.", - "playlistPlay": "Dodano playlistฤ™ [`{0}`] z `{1}` pozycjami do kolejki.", - "playlistOverText": "Bล‚ad! Nazwa playlisty nie moลผe byฤ‡ dล‚uลผsza niลผ 10 znakรณw.", - "playlistSameName": "Bล‚ad! Nowa nazwa musi siฤ™ rรณลผniฤ‡ od starej.", - "playlistDeleteError": "Nie moลผesz usuwaฤ‡ domyล›lnej playlisty", - "playlistRemove": "Usunฤ…ล‚eล› playlistฤ™ [`{0}`].", - "playlistSendErrorPlayer": "Bล‚ฤ…d! Nie moลผesz wysล‚aฤ‡ zaproszenia do samego siebie.", - "playlistSendErrorBot": "Bล‚ฤ…d! Nie moลผesz wysyล‚aฤ‡ zaproszeล„ do bota.", - "playlistBelongs": "Bล‚ฤ…d! Ta playlista naleลผy do <@{0}>.", - "playlistShare": "Bล‚ฤ…d! Ta playlista zostaล‚a udostฤ™pniona {0}.", - "playlistSent": "Bล‚ฤ…d! Zaproszenie zostaล‚o juลผ wczeล›niej wysล‚ane.", - "noPlaylistAcc": "{0} nie stworzyล‚ konta do przechowywania playlist.", - "overPlaylistCreation": "Nie moลผesz stworzyฤ‡ wiฤ™cej niลผ `{0}` playlist!", - "playlistExists": "Playlista [`{0}`] juลผ istnieje.", - "playlistNotInvalidUrl": "Podaj poprawny link do playlisty z Spotify lub Youtube.", - "playlistCreated": "Stworzyล‚eล› playlistฤ™: `{0}`. Uลผyj /playlist view by uzyskaฤ‡ wiฤ™cej informacji.", - "playlistRenamed": "Zmieniล‚eล› nazwฤ™ `{0}` na `{1}`.", - "playlistLimitTrack": "Osiฤ…gnฤ…ล‚eล› limit! Moลผesz dodaฤ‡ maksymalnie `{0}` pozycji do playlisty.", - "playlistPlaylistLink": "Linki do playlist sฤ… nie obsล‚ugiwane.", - "playlistStream": "Nie moลผesz dodaฤ‡ transmisji na ลผywo do playlisty.", - "playlistPositionNotFound": "Pozycja `{0}` z playlisty [`{1}`] nie zostaล‚a znaleziona!", - "playlistRemoved": "๐Ÿ‘‹ Usuniฤ™to **{0}** z playlisty [`{2}`] uลผytkownika {1}.", - "playlistClear": "Pomyล›lnie wyczyล›ciล‚eล› swoja playlistฤ™: [`{0}`].", - "playlistView": "Szczegรณล‚y playlisty", - "playlistViewDesc": "```Nazwa | ID: {0} | {1}\nLiczba utworรณw: {2}\nWล‚aล›ciciel: {3}\nTyp: {4}\n```", - "playlistViewPermsValue": "๐Ÿ“– Odczyt: โœ“ โœ๐Ÿฝ Zapis: {0} ๐Ÿ—‘๏ธ Usuwanie: {1}", - "playlistViewPermsValue2": "๐Ÿ“– Odczyt: {0}", - "playlistViewTrack": "Utwory", - "playlistViewPage": "Strona: {0}/{1} | Caล‚kowity czas trwania: {2}", - "inboxFull": "Bล‚ฤ…d! Skrzynka odbiorcza {0} jest peล‚na.", - "inboxNoMsg": "W twojej skrzynce odbiorczej nie ma wiadomoล›ci.", - "invitationSent": "Zaproszenie wysล‚ane do {0}.", - "notInChannel": "{0}, musisz byฤ‡ na kanale {1} by uลผywaฤ‡ komend gล‚osowych. Jeลผeli jesteล› na kanale poล‚ฤ…cz siฤ™ ponownie!", - "noTrackPlaying": "Aktualnie nic nie jest odtwarzane", - "noTrackFound": "Nie znaleziono utworu! Podaj poprawny adres URL.", - "noLinkSupport": "Komenda /search nie obsล‚uguje linkรณw!", - "voted": "Zagล‚osowaล‚eล›!", - "missingPosPerm": "Tylko DJ lub administracja moลผe zmieniaฤ‡ pozycje.", - "missingModePerm": "Tylko DJ lub administracja moลผe zmieniaฤ‡ tryb pฤ™tli.", - "missingQueuePerm": "Tylko DJ lub administracja moลผe usuwaฤ‡ pozycje z kolejki.", - "missingAutoPlayPerm": "Tylko DJ lub administracja moลผe wล‚ฤ…czaฤ‡ lub wyล‚ฤ…czaฤ‡ tryb autoplay!", - "missingFunctionPerm": "Tylko DJ lub administracja moลผe uลผywaฤ‡ tej funkcji.", - "timeFormatError": "Niepoprawny format czasu. Przykล‚ad: 2:42 lub 12:39:31", - "lyricsNotFound": "Tekst nie zostaล‚ znaleziony. Uลผyj /lyrics by znaleลบฤ‡ tekst piosenki.", - "missingTrackInfo": "Brakuje niektรณrych informacji o utworze.", - "noVoiceChannel": "Kanaล‚ nie zostaล‚ znaleziony", - "playlistAddError": "Niemoลผesz dodawaฤ‡ transmisji na ลผywo do playlisty!", - "playlistAddError2": "Podczas dodawania utworu do playlisty wystฤ…piล‚ bล‚ฤ…d!", - "playlistLimited": "Osiฤ…gnฤ…ล‚eล› limit! Moลผesz dodaฤ‡ maksymalnie `{0}` pozycji do playlisty.", - "playlistRepeated": "Ten utwรณr znajduje sie juลผ na playliล›cie!", - "playlistAdded": "โค๏ธ Dodano **{0}** do playlisty [`{2}`] uลผytkownika {1}!", - "playerDropdown": "Wybierz pozycjฤ™ do ktรณrej pominฤ…ฤ‡ ...", - "playerFilter": "Wybierz efekt ktรณry chesz naล‚oลผyฤ‡ ...", - "buttonBack": "Cofnij", - "buttonPause": "Pauza", - "buttonResume": "Wznรณw", - "buttonSkip": "Pomiล„", - "buttonLeave": "Opuล›ฤ‡", - "buttonLoop": "Pฤ™tla", - "buttonVolumeUp": "Gล‚oล›noล›ฤ‡ +", - "buttonVolumeDown": "Gล‚oล›noล›ฤ‡ -", - "buttonVolumeMute": "Wycisz", - "buttonVolumeUnmute": "Odcisz", - "buttonAutoPlay": "Autoodtwarzanie", - "buttonShuffle": "Losowe odtwarzanie", - "buttonForward": "Przewiล„ do przodu", - "buttonRewind": "Przewiล„ do tyล‚u", - "buttonLyrics": "Tekst utworu", - "nowplayingDesc": "**Aktualnie odtwarzane:**\n```{0}```", - "nowplayingField": "Nastฤ™pne:", - "nowplayingLink": "Sล‚uchaj na: {0}", - "connect": "Poล‚ฤ…czono do {0}", - "live": "LIVE", - "playlistLoad": " ๐ŸŽถ Dodano playlistฤ™ **{0}** z `{1}` pozycjami do kolejki.", - "trackLoad": "Rozpoczฤ™to odtwarzanie **[{0}](<{1}>)** autorstwa **{2}** (`{3}`).\n", - "trackLoad_pos": "Dodano **[{0}](<{1}>)** autorstwa **{2}** (`{3}`) do kolejki na pozycji **{4}**\n", - "searchTitle": "Wyszukiwana fraza: {0}", - "searchDesc": "โžฅ Platforma: {0} **{1}**\nโžฅ Wyniki: **{2}**\n\n{3}", - "searchWait": "Wybierz utwรณr, ktรณry chcesz dodaฤ‡ do playlisty.", - "searchTimeout": "Upล‚ynฤ…ล‚ czas wyszukiwania. Sprรณbuj ponownie pรณลบniej.", - "searchSuccess": "Dodano utwรณr do kolejki.", - "queueTitle": "Nadchodzฤ…ca kolejka:", - "historyTitle": "Historia kolejki:", - "viewTitle": "Kolejka utworรณw", - "viewDesc": "**Aktualnie odtwarzane: [Kliknij mnie]({0}) โฎฏ**\n{1}", - "viewFooter": "Strona: {0}/{1} | Caล‚kowity czas trwania: {2}", - "pauseError": "Odtwarzacz jest juลผ zapauzowany.", - "pauseVote": "{0} zagล‚osowaล‚ nad zapauzowaniem odtwarzania. [{1}/{2}]", - "paused": "`{0}` zapauzowaล‚ odtwarznaie.", - "resumeError": "Odtwarzacz nie jest zapauzowany.", - "resumeVote": "{0} zagล‚osowaล‚ nad wznowieniem odtwarzania. [{1}/{2}]", - "resumed": "`{0}` wznowiล‚ odtwarzanie.", - "shuffleError": "Dodaj wiฤ™cej utworรณw przed przemieszaniem kolejki.", - "shuffleVote": "{0} zagล‚osowaล‚ nad przemieszaniem kolejki. [{1}/{2}]", - "shuffled": "Kolejka zostaล‚a przemieszana.", - "skipError": "Nie ma utworu, do ktรณrego moลผna by pominฤ…ฤ‡.", - "skipVote": "{0} zagล‚osowaล‚ za pominiฤ™ciem tego utworu. [{1}/{2}]", - "skipped": "`{0}` pominฤ…ล‚ utwรณr.", - "backVote": "{0} zagล‚osowaล‚ za przejล›ciem do poprzedniego utworu. [{1}/{2}]", - "backed": "`{0}` cofnฤ…ล‚ do poprzedniego utworu.", - "leaveVote": "{0} zagล‚osowaล‚ nad zatrzyamniem odtwarzaza. [{1}/{2}]", - "left": "`{0}` zatrzymaล‚ odtwarzacz.", - "seek": "Ustawiono odtwarzacz na **{0}**", - "repeat": "Tryb pฤ™tli zostaล‚ ustawiony na: `{0}`", - "cleared": "Wyczyszczono wszystkie utwory w: `{0}`", - "removed": "`{0}` utworรณw zostaล‚o usuniฤ™te z kolejki.", - "forward": "Przewiniฤ™to odtwarzacz do **{0}**", - "rewind": "Cofniฤ™to odtwarzacz do **{0}**", - "replay": "Ponowne odtwarzanie bieลผฤ…cego utworu.", - "swapped": "`{0}` oraz `{1}` sฤ… zamienione", - "moved": "Przeniesiono `{0}` do `{1}`", - "autoplay": "Tryb autoodwarzania jest teraz **{0}**", - "notdj": "Nie jesteล› DJ'em. Obecnym DJ'em jest {0}.", - "djToMe": "Nie moลผesz przekazaฤ‡ roli DJ'a sobie lub botu.", - "djNotInChannel": "`{0}` nie jest na kanale gล‚osowym.", - "djswap": "Przekazaล‚eล› rolฤ™ DJ'a uลผytkownikowi `{0}`.", - "voicelinkQueueFull": "Przepraszamy osiฤ…gniฤ™to limit `{0}` pozycji w kolejce!", - "voicelinkOutofList": "Podaj wล‚aล›ciwy indeks utworu!", - "voicelinkDuplicateTrack": "Bล‚ฤ…d! Ten utwรณr znajduje siฤ™ juลผ w kolejce.", - "decodeError": "Coล› poszล‚o nie tak podczas dekodowania pliku!", - "invalidStartTime": "Niepoprawny czas startu, musi on mieล›ciฤ‡ siฤ™ pomiฤ™dzy `00:00` a `{0}`", - "invalidEndTime": "Niepoprawny czas koล„ca, musi on mieล›ciฤ‡ siฤ™ pomiฤ™dzy `00:00` a `{0}`", - "invalidTimeOrder": "Czas zakoล„czenia odtwarzania nie moลผe byฤ‡ mniejszy lub rรณwny czasowi startu", - "setStageAnnounceTemplate": "Gotowe! Od tego momentu status kanaล‚u w ktรณrym znajduje siฤ™ bot bฤ™dzie wyglฤ…daล‚ tak jak podany szablon. Aktualizacja powinna nastฤ…piฤ‡ za kilka sekund.", - "createSongRequestChannel": "Utworzono kanaล‚ do obsล‚ugi bota - ({0})! Moลผesz w nim dodawaฤ‡ utwory po nazwie lub adresie URL bez dodawnia prefixu" -} \ No newline at end of file + "common": { + "errors": { + "unknown": "โš ๏ธ Coล› poszล‚o nie tak podczas wykonywania polecenia. Sprรณbuj ponownie pรณลบniej lub doล‚ฤ…ฤ‡z do serwera discord w celu uzyskania pomocy.", + "missingIntents": "Niestety to polecenie nie moลผe zostaฤ‡ wykonane, poniewaลผ bot nie posiada wymaganej intencji ลผฤ…dania: `({0})`." + }, + "status": { + "enabled": "wล‚ฤ…czony", + "disabled": "wyล‚ฤ…czony", + "live": "LIVE" + } + }, + "player": { + "errors": { + "nodeReconnect": "Sprรณbuj ponownie! Trwa ponowne ล‚ฤ…cznie z node'm.", + "noPlaySource": "Nie moลผna znaleลบฤ‡ ลผadnych odtwarzalnych ลบrรณdeล‚!", + "noPlayer": "Nie znaleziono ลผadnego odtwarzacza na tym serwerze.", + "noTrackPlaying": "Aktualnie nic nie jest odtwarzane", + "noTrackFound": "Nie znaleziono utworu! Podaj poprawny adres URL." + }, + "dropdown": { + "trackSelect": "Wybierz pozycjฤ™ do ktรณrej pominฤ…ฤ‡ ...", + "filterSelect": "Wybierz efekt ktรณry chesz naล‚oลผyฤ‡ ..." + }, + "buttons": { + "back": "Cofnij", + "pause": "Pauza", + "resume": "Wznรณw", + "skip": "Pomiล„", + "leave": "Opuล›ฤ‡", + "loop": "Pฤ™tla", + "volumeUp": "Gล‚oล›noล›ฤ‡ +", + "volumeDown": "Gล‚oล›noล›ฤ‡ -", + "volumeMute": "Wycisz", + "volumeUnmute": "Odcisz", + "autoPlay": "Autoodtwarzanie", + "shuffle": "Losowe odtwarzanie", + "forward": "Przewiล„ do przodu", + "rewind": "Przewiล„ do tyล‚u", + "lyrics": "Tekst utworu" + }, + "playback": { + "nowplayingDesc": "**Aktualnie odtwarzane:**\n```{0}```", + "nowplayingField": "Nastฤ™pne:", + "nowplayingLink": "Sล‚uchaj na: {0}", + "playlistLoad": " ๐ŸŽถ Dodano playlistฤ™ **{0}** z `{1}` pozycjami do kolejki.", + "trackLoad": "Rozpoczฤ™to odtwarzanie **[{0}](<{1}>)** autorstwa **{2}** (`{3}`).\n", + "trackLoadPos": "Dodano **[{0}](<{1}>)** autorstwa **{2}** (`{3}`) do kolejki na pozycji **{4}**\n" + }, + "controls": { + "pause": { + "error": "Odtwarzacz jest juลผ zapauzowany.", + "vote": "{0} zagล‚osowaล‚ nad zapauzowaniem odtwarzania. [{1}/{2}]", + "success": "`{0}` zapauzowaล‚ odtwarznaie." + }, + "resume": { + "error": "Odtwarzacz nie jest zapauzowany.", + "vote": "{0} zagล‚osowaล‚ nad wznowieniem odtwarzania. [{1}/{2}]", + "success": "`{0}` wznowiล‚ odtwarzanie." + }, + "shuffle": { + "error": "Dodaj wiฤ™cej utworรณw przed przemieszaniem kolejki.", + "vote": "{0} zagล‚osowaล‚ nad przemieszaniem kolejki. [{1}/{2}]", + "success": "Kolejka zostaล‚a przemieszana." + }, + "skip": { + "error": "Nie ma utworu, do ktรณrego moลผna by pominฤ…ฤ‡.", + "vote": "{0} zagล‚osowaล‚ za pominiฤ™ciem tego utworu. [{1}/{2}]", + "success": "`{0}` pominฤ…ล‚ utwรณr." + }, + "back": { + "vote": "{0} zagล‚osowaล‚ za przejล›ciem do poprzedniego utworu. [{1}/{2}]", + "success": "`{0}` cofnฤ…ล‚ do poprzedniego utworu." + }, + "leave": { + "vote": "{0} zagล‚osowaล‚ nad zatrzyamniem odtwarzaza. [{1}/{2}]", + "success": "`{0}` zatrzymaล‚ odtwarzacz." + }, + "seek": "Ustawiono odtwarzacz na **{0}**", + "repeat": "Tryb pฤ™tli zostaล‚ ustawiony na: `{0}`", + "forward": "Przewiniฤ™to odtwarzacz do **{0}**", + "rewind": "Cofniฤ™to odtwarzacz do **{0}**", + "replay": "Ponowne odtwarzanie bieลผฤ…cego utworu.", + "autoplay": "Tryb autoodwarzania jest teraz **{0}**" + } + }, + "voice": { + "connection": { + "noChannel": "Brak kanaล‚u gล‚osowego do doล‚ฤ…cznia. Doล‚ฤ…cz do jakiegoล›, lub wskaลผ kanaล‚ komendฤ… /connect", + "alreadyConnected": "Juลผ poล‚ฤ…czono z kanaล‚em gล‚osowym.", + "noPermission": "Niestety nie mam uprawnieล„, aby doล‚ฤ…czyฤ‡ do Twojego kanaล‚u gล‚osowego lub na nim mรณwiฤ‡.", + "notInChannel": "{0}, musisz byฤ‡ na kanale {1} by uลผywaฤ‡ komend gล‚osowych. Jeลผeli jesteล› na kanale poล‚ฤ…cz siฤ™ ponownie!", + "connect": "Poล‚ฤ…czono do {0}" + }, + "stageChannel": { + "setAnnounceTemplate": "Gotowe! Od tego momentu status kanaล‚u w ktรณrym znajduje siฤ™ bot bฤ™dzie wyglฤ…daล‚ tak jak podany szablon. Aktualizacja powinna nastฤ…piฤ‡ za kilka sekund." + } + }, + "permissions": { + "noCreatePermission": "Niestety, nie mam uprawnieล„, aby stworzyฤ‡ kanaล‚ do ลผฤ…dania utworรณw.", + "missingPosition": "Tylko DJ lub administracja moลผe zmieniaฤ‡ pozycje.", + "missingMode": "Tylko DJ lub administracja moลผe zmieniaฤ‡ tryb pฤ™tli.", + "missingQueue": "Tylko DJ lub administracja moลผe usuwaฤ‡ pozycje z kolejki.", + "missingAutoPlay": "Tylko DJ lub administracja moลผe wล‚ฤ…czaฤ‡ lub wyล‚ฤ…czaฤ‡ tryb autoplay!", + "missingFunction": "Tylko DJ lub administracja moลผe uลผywaฤ‡ tej funkcji.", + "notDj": "Nie jesteล› DJ'em. Obecnym DJ'em jest {0}.", + "djToSelf": "Nie moลผesz przekazaฤ‡ roli DJ'a sobie lub botu.", + "djNotInChannel": "`{0}` nie jest na kanale gล‚osowym.", + "djSwapped": "Przekazaล‚eล› rolฤ™ DJ'a uลผytkownikowi `{0}`." + }, + "settings": { + "actions": { + "languageNotFound": "Nie znaleziono pakietu jฤ™zykowego! wybierz istniejฤ…cy pakiet jฤ™zykowy.", + "languageChanged": "Pomyล›lnie zmieniono pakiet jฤ™zykowy na: `{0}` .", + "prefixSet": "Gotowe! Mรณj prefix na tym serwerze to teraz `{0}`. Sprรณbuj uruchomiฤ‡ polecenie `{1}ping`, aby go przetestowaฤ‡.", + "djSet": "Ustawiono DJ jako {0}.", + "queueModeSet": "Zmienionio tryb kolejki na `{0}`.", + "mode247": "Tryb 24/7: `{0}`", + "bypassVote": "System gล‚osowania: `{0}`", + "volumeSet": "Ustawiono gล‚oล›noล›ฤ‡ na `{0}`%", + "controllerToggled": "Kontroler muzyki: `{0}`", + "duplicateTrackToggled": "Unikanie duplikowania pozycji w kolejce: `{0}`", + "controllerMsgToggled": "Wiadomoล›ci od kontrolera muzyki: `{0}`", + "silentMsgToggled": "Masz {0} ciche wiadomoล›ci." + }, + "menu": "Ustawnienia serwera | {0}", + "basic": { + "title": "โค๏ธ Podstawowe informacje:", + "value": "```Prefix: {0}\nJฤ™zyk: {1}\nKontroler muzyki: {2}\nRola DJ: @{3}\nOminiฤ™cie gล‚osowania: {4}\n24/7: {5}\nDomyล›lna gล‚oล›noล›ฤ‡: {6}%\nCzas odtwarzania: {7}```" + }, + "queue": { + "title": "๐Ÿ”— Informacje o kolejce:", + "value": "```Tryb kolejki: {0}\nMaksymlna liczba utworรณw: {1}\nZduplikowane pozycje: {2}```" + }, + "voice": { + "title": "๐ŸŽค Informacie w statusie kanaล‚u gล‚osowego:" + }, + "permissions": { + "title": "โœจ Uprawnienia:", + "value": "```{0} Administrator\n{1} Zarzฤ…dzanie_serwerem\n{2} Zarzฤ…dzanie_kanaล‚em\n{3} Zarzฤ…dzanie_wiadomoล›ciami```" + } + }, + "ping": { + "title1": "Informacje o bocie:", + "title2": "Infomracje o odtwarzaczu:", + "field1": "```ID Shard'a: {0}/{1}\nOpรณลบnienie shard'a: {2:.3f}s {3}\nRegion: {4}```", + "field2": "```Wฤ™zeล‚: {0} - {1:.3f}s\nOdtwarzacze: {2}\nRegion: {3}```" + }, + "effects": { + "added": "Naล‚oลผono efekt: `{0}`", + "cleared": "Efekty dลบwiฤ™kowe zostaล‚y wyczyszczone.", + "tagInUse": "Ten efekt dลบwiฤ™kowy jest juลผ w uลผywany! Uลผyj /cleareffect aby go wyล‚ฤ…czyฤ‡." + }, + "playlist": { + "view": { + "title": "Playlisty uลผytkownika {0}:", + "headers": "ID:,Czas:,Nazwa:,Iloล›ฤ‡ pozycji:", + "footer": "Uลผyj /playlist play [nazwa_playlisty] by dodaฤ‡ playlistฤ™ do kolejki.", + "detailTitle": "Szczegรณล‚y playlisty", + "detailDesc": "```Nazwa: {0} [{1}]\nLiczba utworรณw: {2}\nWล‚aล›ciciel: {3}\nTyp: {4}\n```", + "permsValue": "๐Ÿ“– Odczyt: โœ“ โœ๐Ÿฝ Zapis: {0} ๐Ÿ—‘๏ธ Usuwanie: {1}", + "permsValue2": "๐Ÿ“– Odczyt: {0}", + "trackList": "Utwory", + "footer2": "Caล‚kowity czas trwania: {0}", + "playlistSelect": "Wybierz listฤ™ odtwarzania do wyล›wietlenia ..." + }, + "errors": { + "notFound": "Playlista [`{0}`] nie zostaล‚a znaleziona. Uลผyj /playlist view by zobaczyฤ‡ wszystkie swoje playlisty.", + "noAccess": "Przepraszamy! Nie masz uprawnieล„ do tej playlisty!", + "noTrack": "Bล‚ad! W playliล›cie [`{0}`] nie ma utworรณw.", + "notAllowed": "Ta komenda jest niedozwolona w odniesienu do udostฤ™pnionych playlist.", + "nameOverLimit": "Bล‚ad! Nazwa playlisty nie moลผe byฤ‡ dล‚uลผsza niลผ 10 znakรณw.", + "sameName": "Bล‚ad! Nowa nazwa musi siฤ™ rรณลผniฤ‡ od starej.", + "deleteDefault": "Nie moลผesz usuwaฤ‡ domyล›lnej playlisty", + "limitReached": "Nie moลผesz stworzyฤ‡ wiฤ™cej niลผ `{0}` playlist!", + "exists": "Playlista [`{0}`] juลผ istnieje.", + "invalidUrl": "Podaj poprawny link do playlisty z Spotify lub Youtube.", + "trackLimitReached": "Osiฤ…gnฤ…ล‚eล› limit! Moลผesz dodaฤ‡ maksymalnie `{0}` pozycji do playlisty.", + "playlistLinkNotAllowed": "Linki do playlist sฤ… nie obsล‚ugiwane.", + "streamNotAllowed": "Nie moลผesz dodaฤ‡ transmisji na ลผywo do playlisty.", + "positionNotFound": "Pozycja `{0}` z playlisty [`{1}`] nie zostaล‚a znaleziona!", + "streaming": "Niemoลผesz dodawaฤ‡ transmisji na ลผywo do playlisty!", + "track": "Podczas dodawania utworu do playlisty wystฤ…piล‚ bล‚ฤ…d!", + "trackLimited": "Osiฤ…gnฤ…ล‚eล› limit! Moลผesz dodaฤ‡ maksymalnie `{0}` pozycji do playlisty.", + "trackRepeated": "Ten utwรณr znajduje sie juลผ na playliล›cie!" + }, + "actions": { + "play": "Dodano playlistฤ™ [`{0}`] z `{1}` pozycjami do kolejki.", + "removed": "Usunฤ…ล‚eล› playlistฤ™ [`{0}`].", + "created": "Stworzyล‚eล› playlistฤ™: `{0}`. Uลผyj /playlist view by uzyskaฤ‡ wiฤ™cej informacji.", + "renamed": "Zmieniล‚eล› nazwฤ™ `{0}` na `{1}`.", + "trackRemoved": "๐Ÿ‘‹ Usuniฤ™to **{0}** z playlisty [`{2}`] uลผytkownika {1}.", + "cleared": "Pomyล›lnie wyczyล›ciล‚eล› swoja playlistฤ™: [`{0}`].", + "trackAdded": "โค๏ธ Dodano **{0}** do playlisty [`{2}`] uลผytkownika {1}!" + }, + "sharing": { + "sendErrorPlayer": "Bล‚ฤ…d! Nie moลผesz wysล‚aฤ‡ zaproszenia do samego siebie.", + "sendErrorBot": "Bล‚ฤ…d! Nie moลผesz wysyล‚aฤ‡ zaproszeล„ do bota.", + "belongs": "Bล‚ฤ…d! Ta playlista naleลผy do <@{0}>.", + "alreadyShared": "Bล‚ฤ…d! Ta playlista zostaล‚a udostฤ™pniona {0}.", + "alreadySent": "Bล‚ฤ…d! Zaproszenie zostaล‚o juลผ wczeล›niej wysล‚ane.", + "noAccount": "{0} nie stworzyล‚ konta do przechowywania playlist.", + "invitationSent": "Zaproszenie wysล‚ane do {0}." + }, + "inbox": { + "full": "Bล‚ฤ…d! Skrzynka odbiorcza {0} jest peล‚na.", + "noMessages": "W twojej skrzynce odbiorczej nie ma wiadomoล›ci." + } + }, + "search": { + "noLinkSupport": "Komenda /search nie obsล‚uguje linkรณw!", + "title": "Wyszukiwana fraza: {0}", + "desc": "โžฅ Platforma: {0} **{1}**\nโžฅ Wyniki: **{2}**\n\n{3}", + "wait": "Wybierz utwรณr, ktรณry chcesz dodaฤ‡ do playlisty.", + "success": "Dodano utwรณr do kolejki." + }, + "voting": { + "voted": "Zagล‚osowaล‚eล›!" + }, + "time": { + "formatError": "Niepoprawny format czasu. Przykล‚ad: 2:42 lub 12:39:31", + "invalidStartTime": "Niepoprawny czas startu, musi on mieล›ciฤ‡ siฤ™ pomiฤ™dzy `00:00` a `{0}`", + "invalidEndTime": "Niepoprawny czas koล„ca, musi on mieล›ciฤ‡ siฤ™ pomiฤ™dzy `00:00` a `{0}`" + }, + "lyrics": { + "notFound": "Tekst nie zostaล‚ znaleziony. Uลผyj /lyrics by znaleลบฤ‡ tekst piosenki." + }, + "queue": { + "management": { + "title": "Nadchodzฤ…ca kolejka:", + "cleared": "Wyczyszczono wszystkie utwory w: `{0}`", + "removed": "`{0}` utworรณw zostaล‚o usuniฤ™te z kolejki.", + "swapped": "`{0}` oraz `{1}` sฤ… zamienione", + "moved": "Przeniesiono `{0}` do `{1}`" + }, + "view": { + "historyTitle": "Historia kolejki:", + "title": "Kolejka utworรณw", + "desc": "**Aktualnie odtwarzane: [Kliknij mnie]({0}) โฎฏ**\n{1}" + }, + "errors": { + "queueFull": "Przepraszamy osiฤ…gniฤ™to limit `{0}` pozycji w kolejce!", + "outOfList": "Podaj wล‚aล›ciwy indeks utworu!", + "duplicateTrack": "Bล‚ฤ…d! Ten utwรณr znajduje siฤ™ juลผ w kolejce." + } + }, + "songRequest": { + "channelCreated": "Utworzono kanaล‚ do obsล‚ugi bota - ({0})! Moลผesz w nim dodawaฤ‡ utwory po nazwie lub adresie URL bez dodawnia prefixu" + }, + "pagination": { + "prev": "Poprzedni", + "next": "Nastฤ™pny", + "play": "Odtwรณrz", + "share": "Udostฤ™pnij", + "export": "Eksportuj", + "delete": "Usuล„", + "page": { + "title": "Wprowadลบ numer strony", + "field": "Numer strony" + } + } +} diff --git a/langs/RU.json b/langs/RU.json index 2eb340b..804d4c6 100644 --- a/langs/RU.json +++ b/langs/RU.json @@ -1,172 +1,262 @@ { - "unknownException": "โš ๏ธ ะงั‚ะพ-ั‚ะพ ะฟะพัˆะปะพ ะฝะต ั‚ะฐะบ ะฟั€ะธ ะฒั‹ะฟะพะปะฝะตะฝะธะธ ะบะพะผะฐะฝะดั‹! ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฟะพะฟั€ะพะฑัƒะนั‚ะต ะฟะพะทะถะต ะธะปะธ ะฟั€ะธัะพะตะดะธะฝะธั‚ะตััŒ ะบ ะฝะฐัˆะตะผัƒ ัะตั€ะฒะตั€ัƒ Discord ะดะปั ะฟะพะปัƒั‡ะตะฝะธั ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝะพะน ะฟะพะดะดะตั€ะถะบะธ.", - "enabled": "ะฒะบะป", - "disabled": "ะฒั‹ะบะป", - "nodeReconnect": "ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฟะพะฟั€ะพะฑัƒะนั‚ะต ั‡ัƒั‚ัŒ ะฟะพะทะถะต!", - "noChannel": "ะะตั‚ ะณะพะปะพัะพะฒะพะณะพ ะบะฐะฝะฐะปะฐ ะดะปั ะฟะพะดะบะปัŽั‡ะตะฝะธั. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ัƒะบะฐะถะธั‚ะต ะธะปะธ ะฟั€ะธัะพะตะดะธะฝะธั‚ะตััŒ ะบ ะพะดะฝะพะผัƒ.", - "alreadyConnected": "ะฃะถะต ะฟะพะดะบะปัŽั‡ะตะฝ ะบ ะณะพะปะพัะพะฒะพะผัƒ ะบะฐะฝะฐะปัƒ.", - "noPermission": "ะ˜ะทะฒะธะฝะธั‚ะต! ะฃ ะผะตะฝั ะฝะตั‚ ั€ะฐะทั€ะตัˆะตะฝะธั ะฝะฐ ะฟะพะดะบะปัŽั‡ะตะฝะธะต ะธะปะธ ั€ะฐะทะณะพะฒะพั€ ะฒ ะฒะฐัˆะตะผ ะณะพะปะพัะพะฒะพะผ ะบะฐะฝะฐะปะต.", - "noCreatePermission": "ะ˜ะทะฒะธะฝะธั‚ะต! ะฃ ะผะตะฝั ะฝะตั‚ ะฟั€ะฐะฒ ะฝะฐ ัะพะทะดะฐะฝะธะต ะบะฐะฝะฐะปะฐ ะดะปั ะทะฐะฟั€ะพัะฐ ะฟะตัะตะฝ.", - "noPlaySource": "ะะต ะฟะพะปัƒั‡ะธะปะพััŒ ะฝะฐะนั‚ะธ ั€ะฐะฑะพั‡ะธะต ะธัั‚ะพั‡ะฝะธะบะธ!", - "noPlayer": "ะะฐ ัั‚ะพะผ ัะตั€ะฒะตั€ะต ะฝะต ะฝะฐะนะดะตะฝะพ ะฝะธ ะพะดะฝะพะณะพ ะฐะบั‚ะธะฒะฝะพะณะพ ะฟะปะตะตั€ะฐ.", - "notVote": "ะญั‚ะฐ ะบะพะผะฐะฝะดะฐ ั‚ั€ะตะฑัƒะตั‚ ะฒะฐัˆะตะณะพ ะณะพะปะพัะฐ! ะ’ะฒะตะดะธั‚ะต `/vote` ะดะปั ะฟะพะปัƒั‡ะตะฝะธั ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝะพะน ะธะฝั„ะพั€ะผะฐั†ะธะธ.", - "missingIntents": "ะ˜ะทะฒะธะฝะธั‚ะต, ะฝะพ ัั‚ัƒ ะบะพะผะฐะฝะดัƒ ะฝะตะปัŒะทั ะฒั‹ะฟะพะปะฝะธั‚ัŒ, ั‚ะฐะบ ะบะฐะบ ัƒ ะฑะพั‚ะฐ ะพั‚ััƒั‚ัั‚ะฒัƒะตั‚ ะฝะตะพะฑั…ะพะดะธะผั‹ะน ะทะฐะฟั€ะพั ะฝะฐะผะตั€ะตะฝะธั: `({0})`.", - "languageNotFound": "ะฏะทั‹ะบะพะฒะพะน ะฟะฐะบะตั‚ ะฝะต ะฝะฐะนะดะตะฝ! ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฒั‹ะฑะตั€ะธั‚ะต ััƒั‰ะตัั‚ะฒัƒัŽั‰ะธะน ัะทั‹ะบะพะฒะพะน ะฟะฐะบะตั‚.", - "changedLanguage": "ะฏะทั‹ะบ ัƒัะฟะตัˆะฝะพ ะธะทะผะตะฝะตะฝ ะฝะฐ `{0}`.", - "setPrefix": "ะ“ะพั‚ะพะฒะพ! ะœะพะน ะฟั€ะตั„ะธะบั ะฝะฐ ะฒะฐัˆะตะผ ัะตั€ะฒะตั€ะต ั‚ะตะฟะตั€ัŒ `{0}`. ะŸะพะฟั€ะพะฑัƒะนั‚ะต ะทะฐะฟัƒัั‚ะธั‚ัŒ `{1}ping`, ั‡ั‚ะพะฑั‹ ะฟั€ะพะฒะตั€ะธั‚ัŒ ะตะณะพ.", - "setDJ": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ั€ะพะปัŒ DJ {0}.", - "setQueue": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ั€ะตะถะธะผ ะพั‡ะตั€ะตะดะธ ะฝะฐ `{0}`.", - "247": "ะขะตะฟะตั€ัŒ ัƒ ะฒะฐั `{0}` ั€ะตะถะธะผ 24/7.", - "bypassVote": "ะขะตะฟะตั€ัŒ ัƒ ะฒะฐั `{0}` ัะธัั‚ะตะผะฐ ะณะพะปะพัะพะฒะฐะฝะธั.", - "setVolume": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ะณั€ะพะผะบะพัั‚ัŒ ะฝะฐ `{0}`%", - "toggleController": "ะขะตะฟะตั€ัŒ ัƒ ะฒะฐั `{0}` ะบะพะฝั‚ั€ะพะปะปะตั€ ะผัƒะทั‹ะบะธ.", - "toggleDuplicateTrack": "ะขะตะฟะตั€ัŒ ัƒ ะฒะฐั `{0}` ั„ะธะปัŒั‚ั€ ะดัƒะฑะปะธะบะฐั‚ะพะฒ ะฒ ะพั‡ะตั€ะตะดะธ.", - "toggleControllerMsg": "ะขะตะฟะตั€ัŒ ัƒ ะฒะฐั `{0}` ัะพะพะฑั‰ะตะฝะธั ะพั‚ ะฟะปะตะตั€ะฐ.", - "toggleSilentMsg": "ะฃ ะฒะฐั {0} ั‚ะธั…ะธั… ัะพะพะฑั‰ะตะฝะธะน.", - "settingsMenu": "ะะฐัั‚ั€ะพะนะบะธ ัะตั€ะฒะตั€ะฐ | {0}", - "settingsTitle": "โค๏ธ ะžัะฝะพะฒะฝะฐั ะธะฝั„ะพั€ะผะฐั†ะธั:", - "settingsValue": "```ะŸั€ะตั„ะธะบั: {0}\nะฏะทั‹ะบ: {1}\nะ’ะบะปัŽั‡ะธั‚ัŒ ะบะพะฝั‚ั€ะพะปะปะตั€ ะผัƒะทั‹ะบะธ: {2}\nDJ ั€ะพะปัŒ: @{3}\nะžะฑั…ะพะด ะณะพะปะพัะพะฒะฐะฝะธั: {4}\n24/7: {5}\nะ“ั€ะพะผะบะพัั‚ัŒ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ: {6}%\nะ’ั€ะตะผั ะฟั€ะพะธะณั€ั‹ะฒะฐะฝะธั: {7}```", - "settingsTitle2": "๐Ÿ”— ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพะฑ ะพั‡ะตั€ะตะดะธ:", - "settingsValue2": "```ะ ะตะถะธะผ ะพั‡ะตั€ะตะดะธ: {0}\nะœะฐะบัะธะผะฐะปัŒะฝะพะต ะบะพะปะธั‡ะตัั‚ะฒะพ ะฟะตัะตะฝ: {1}\nะ ะฐะทั€ะตัˆะธั‚ัŒ ะดัƒะฑะปะธั€ะพะฒะฐะฝะธะต ั‚ั€ะตะบะพะฒ: {2}```", - "settingsTitle3": "๐ŸŽค ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ัะพัั‚ะพัะฝะธะธ ะณะพะปะพัะฐ:", - "settingsPermTitle": "โœจ ะŸั€ะฐะฒะฐ:", - "settingsPermValue": "```{0} ะะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€\n{1} ะฃะฟั€ะฐะฒะปะตะฝะธะต_ะกะตั€ะฒะตั€ะพะผ\n{2} ะฃะฟั€ะฐะฒะปะตะฝะธะต_ะšะฐะฝะฐะปะพะผ\n{3} ะฃะฟั€ะฐะฒะปะตะฝะธะต_ะกะพะพะฑั‰ะตะฝะธัะผะธ```", - "pingTitle1": "ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ะฑะพั‚ะต:", - "pingTitle2": "ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ะฟะปะตะตั€ะต:", - "pingField1": "```ID ะจะฐั€ะดะฐ: {0}/{1}\nะ—ะฐะดะตั€ะถะบะฐ ะจะฐั€ะดะฐ: {2:.3f}s {3}\nะ ะตะณะธะพะฝ: {4}```", - "pingField2": "```ะกะตั€ะฒะธั: {0} - {1:.3f}s\nะ˜ะณั€ะพะบะธ: {2}\nะ ะตะณะธะพะฝ ะ“ะพะปะพัะฐ: {3}```", - "addEffect": "ะŸั€ะธะผะตะฝะตะฝ `{0}` ัั„ั„ะตะบั‚.", - "clearEffect": "ะ—ะฒัƒะบะพะฒั‹ะต ัั„ั„ะตะบั‚ั‹ ะฑั‹ะปะธ ะพั‡ะธั‰ะตะฝั‹!", - "filterTagAlreadyInUse": "ะญั‚ะพั‚ ะทะฒัƒะบะพะฒะพะน ัั„ั„ะตะบั‚ ัƒะถะต ะธัะฟะพะปัŒะทัƒะตั‚ัั! ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะธัะฟะพะปัŒะทัƒะนั‚ะต /cleareffect <ะขะตะณ>, ั‡ั‚ะพะฑั‹ ัƒะดะฐะปะธั‚ัŒ ะตะณะพ.", - "playlistViewTitle": "๐Ÿ“œ ะ’ัะต ะฟะปะตะนะปะธัั‚ั‹ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั {0}", - "playlistViewHeaders": "ID:,ะ’ั€ะตะผั:,ะะฐะทะฒะฐะฝะธะต:,ะขั€ะตะบะธ:", - "playlistFooter": "ะ’ะฒะตะดะธั‚ะต /playlist play [ะฟะปะตะนะปะธัั‚], ั‡ั‚ะพะฑั‹ ะดะพะฑะฐะฒะธั‚ัŒ ะฟะปะตะนะปะธัั‚ ะฒ ะพั‡ะตั€ะตะดัŒ.", - "playlistNotFound": "ะŸะปะตะนะปะธัั‚ [`{0}`] ะฝะต ะฝะฐะนะดะตะฝ. ะ’ะฒะตะดะธั‚ะต /playlist view, ั‡ั‚ะพะฑั‹ ะฟะพัะผะพั‚ั€ะตั‚ัŒ ะฒัะต ะฒะฐัˆะธ ะฟะปะตะนะปะธัั‚ั‹.", - "playlistNotAccess": "ะ˜ะทะฒะธะฝะธั‚ะต! ะฃ ะฒะฐั ะฝะตั‚ ะดะพัั‚ัƒะฟะฐ ะบ ัั‚ะพะผัƒ ะฟะปะตะนะปะธัั‚ัƒ!", - "playlistNoTrack": "ะ˜ะทะฒะธะฝะธั‚ะต! ะ’ ะฟะปะตะนะปะธัั‚ะต [`{0}`] ะฝะตั‚ ั‚ั€ะตะบะพะฒ.", - "playlistNotAllow": "ะญั‚ะฐ ะบะพะผะฐะฝะดะฐ ะฝะต ั€ะฐะทั€ะตัˆะตะฝะฐ ะดะปั ะพะฑั‰ะธั… ะฟะปะตะนะปะธัั‚ะพะฒ.", - "playlistPlay": "ะ”ะพะฑะฐะฒะปะตะฝ ะฟะปะตะนะปะธัั‚ [`{0}`] ั `{1}` ั‚ั€ะตะบะฐะผะธ ะฒ ะพั‡ะตั€ะตะดัŒ.", - "playlistOverText": "ะ˜ะทะฒะธะฝะธั‚ะต! ะ˜ะผั ะฟะปะตะนะปะธัั‚ะฐ ะฝะต ะผะพะถะตั‚ ะฟั€ะตะฒั‹ัˆะฐั‚ัŒ 10 ัะธะผะฒะพะปะพะฒ.", - "playlistSameName": "ะ˜ะทะฒะธะฝะธั‚ะต! ะญั‚ะพ ะธะผั ะฝะต ะผะพะถะตั‚ ัะพะฒะฟะฐะดะฐั‚ัŒ ั ะฒะฐัˆะธะผ ะฝะพะฒั‹ะผ ะธะผะตะฝะตะผ.", - "playlistDeleteError": "ะ’ั‹ ะฝะต ะผะพะถะตั‚ะต ัƒะดะฐะปะธั‚ัŒ ะฟะปะตะนะปะธัั‚ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ.", - "playlistRemove": "ะ’ั‹ ัƒะดะฐะปะธะปะธ ะฟะปะตะนะปะธัั‚ [`{0}`].", - "playlistSendErrorPlayer": "ะ˜ะทะฒะธะฝะธั‚ะต! ะ’ั‹ ะฝะต ะผะพะถะตั‚ะต ะพั‚ะฟั€ะฐะฒะธั‚ัŒ ะฟั€ะธะณะปะฐัˆะตะฝะธะต ัะฐะผะพะผัƒ ัะตะฑะต.", - "playlistSendErrorBot": "ะ˜ะทะฒะธะฝะธั‚ะต! ะ’ั‹ ะฝะต ะผะพะถะตั‚ะต ะพั‚ะฟั€ะฐะฒะธั‚ัŒ ะฟั€ะธะณะปะฐัˆะตะฝะธะต ะฑะพั‚ัƒ.", - "playlistBelongs": "ะ˜ะทะฒะธะฝะธั‚ะต! ะญั‚ะพั‚ ะฟะปะตะนะปะธัั‚ ะฟั€ะธะฝะฐะดะปะตะถะธั‚ <@{0}>.", - "playlistShare": "ะ˜ะทะฒะธะฝะธั‚ะต! ะญั‚ะธะผ ะฟะปะตะนะปะธัั‚ะพะผ ัƒะถะต ะฟะพะดะตะปะธะปะธััŒ ั {0}.", - "playlistSent": "ะ˜ะทะฒะธะฝะธั‚ะต! ะ’ั‹ ัƒะถะต ะพั‚ะฟั€ะฐะฒะธะปะธ ะฟั€ะธะณะปะฐัˆะตะฝะธะต ั€ะฐะฝะตะต.", - "noPlaylistAcc": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฝะต ัะพะทะดะฐะป ัƒั‡ะตั‚ะฝัƒัŽ ะทะฐะฟะธััŒ ะฟะปะตะนะปะธัั‚ะฐ.", - "overPlaylistCreation": "ะ’ั‹ ะฝะต ะผะพะถะตั‚ะต ัะพะทะดะฐะฒะฐั‚ัŒ ะฑะพะปัŒัˆะต `{0}` ะฟะปะตะนะปะธัั‚ะพะฒ!", - "playlistExists": "ะŸะปะตะนะปะธัั‚ [`{0}`] ัƒะถะต ััƒั‰ะตัั‚ะฒัƒะตั‚.", - "playlistNotInvalidUrl": "ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฒะฒะตะดะธั‚ะต ะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝัƒัŽ ััั‹ะปะบัƒ ะธะปะธ ะฟัƒะฑะปะธั‡ะฝัƒัŽ ะฟะปะตะนะปะธัั‚-ััั‹ะปะบัƒ ะฝะฐ Spotify ะธะปะธ YouTube.", - "playlistCreated": "ะ’ั‹ ัะพะทะดะฐะปะธ ะฟะปะตะนะปะธัั‚ `{0}`. ะ’ะฒะตะดะธั‚ะต /playlist view ะดะปั ะฟะพะปัƒั‡ะตะฝะธั ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝะพะน ะธะฝั„ะพั€ะผะฐั†ะธะธ.", - "playlistRenamed": "ะ’ั‹ ะฟะตั€ะตะธะผะตะฝะพะฒะฐะปะธ `{0}` ะฒ `{1}`.", - "playlistLimitTrack": "ะ’ั‹ ะดะพัั‚ะธะณะปะธ ะปะธะผะธั‚ะฐ! ะ’ั‹ ะผะพะถะตั‚ะต ะดะพะฑะฐะฒะธั‚ัŒ ั‚ะพะปัŒะบะพ `{0}` ั‚ั€ะตะบะพะฒ ะฒ ัะฒะพะน ะฟะปะตะนะปะธัั‚.", - "playlistPlaylistLink": "ะ’ะฐะผ ะฝะต ั€ะฐะทั€ะตัˆะตะฝะพ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ััั‹ะปะบัƒ ะฝะฐ ะฟะปะตะนะปะธัั‚.", - "playlistStream": "ะ’ะฐะผ ะฝะต ั€ะฐะทั€ะตัˆะตะฝะพ ะดะพะฑะฐะฒะปัั‚ัŒ ะฟะพั‚ะพะบะพะฒั‹ะต ะฒะธะดะตะพ ะฒ ัะฒะพะน ะฟะปะตะนะปะธัั‚.", - "playlistPositionNotFound": "ะะต ะฟะพะปัƒั‡ะธะปะพััŒ ะฝะฐะนั‚ะธ ะฟะพะทะธั†ะธัŽ `{0}` ะฒ ะฒะฐัˆะตะผ ะฟะปะตะนะปะธัั‚ะต [`{1}`]!", - "playlistRemoved": "๐Ÿ‘‹ ะฃะดะฐะปะตะฝ **{0}** ะธะท ะฟะปะตะนะปะธัั‚ะฐ {1} [`{2}`].", - "playlistClear": "ะ’ั‹ ัƒัะฟะตัˆะฝะพ ะพั‡ะธัั‚ะธะปะธ ัะฒะพะน ะฟะปะตะนะปะธัั‚ [`{0}`].", - "playlistView": "ะŸั€ะพัะผะพั‚ั€ ะฟะปะตะนะปะธัั‚ะพะฒ", - "playlistViewDesc": "```ะ˜ะผั | ID: {0} | {1}\nะ’ัะตะณะพ ั‚ั€ะตะบะพะฒ: {2}\nะ’ะปะฐะดะตะปะตั†: {3}\nะขะธะฟ: {4}\n```", - "playlistViewPermsValue": "๐Ÿ“– ะงั‚ะตะฝะธะต: โœ“ โœ๐Ÿฝ ะ—ะฐะฟะธััŒ: {0} ๐Ÿ—‘๏ธ ะฃะดะฐะปะตะฝะธะต: {1}", - "playlistViewPermsValue2": "๐Ÿ“– ะงั‚ะตะฝะธะต: {0}", - "playlistViewTrack": "ะขั€ะตะบะธ", - "playlistViewPage": "ะกั‚ั€ะฐะฝะธั†ะฐ: {0}/{1} | ะžะฑั‰ะฐั ะฟั€ะพะดะพะปะถะธั‚ะตะปัŒะฝะพัั‚ัŒ: {2}", - "inboxFull": "ะ˜ะทะฒะธะฝะธั‚ะต! ะŸะพั‡ั‚ะพะฒั‹ะน ัั‰ะธะบ {0} ะฟะตั€ะตะฟะพะปะฝะตะฝ.", - "inboxNoMsg": "ะ’ ะฒะฐัˆะตะผ ะฟะพั‡ั‚ะพะฒะพะผ ัั‰ะธะบะต ะฝะตั‚ ัะพะพะฑั‰ะตะฝะธะน.", - "invitationSent": "ะŸั€ะธะณะปะฐัˆะตะฝะธะต ะพั‚ะฟั€ะฐะฒะปะตะฝะพ {0}.", - "notInChannel": "{0}, ะดะปั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั ะณะพะปะพัะพะฒั‹ั… ะบะพะผะฐะฝะด ะฒั‹ ะดะพะปะถะฝั‹ ะฝะฐั…ะพะดะธั‚ัŒัั ะฒ {1}. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฟะตั€ะตะทะฐะนะดะธั‚ะต, ะตัะปะธ ะฒั‹ ัƒะถะต ะฝะฐั…ะพะดะธั‚ะตััŒ ะฒ ะณะพะปะพัะพะฒะพะผ ะบะฐะฝะฐะปะต!", - "noTrackPlaying": "ะกะตะนั‡ะฐั ะฝะตั‚ ะธะณั€ะฐัŽั‰ะธั… ั‚ั€ะตะบะพะฒ.", - "noTrackFound": "ะขั€ะตะบะธ ะฟะพ ั‚ะฐะบะพะผัƒ ะทะฐะฟั€ะพััƒ ะฝะต ะฝะฐะนะดะตะฝั‹! ะŸะพะถะฐะปัƒะนัั‚ะฐ, ัƒะบะฐะถะธั‚ะต ะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝัƒัŽ ััั‹ะปะบัƒ.", - "noLinkSupport": "ะšะพะผะฐะฝะดะฐ ะฟะพะธัะบะฐ ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ััั‹ะปะบะธ!", - "voted": "ะ’ั‹ ะฟั€ะพะณะพะปะพัะพะฒะฐะปะธ!", - "missingPosPerm": "ะขะพะปัŒะบะพ DJ ะธะปะธ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ั‹ ะผะพะณัƒั‚ ะธะทะผะตะฝัั‚ัŒ ะฟะพะทะธั†ะธัŽ.", - "missingModePerm": "ะขะพะปัŒะบะพ DJ ะธะปะธ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ั‹ ะผะพะณัƒั‚ ะฟะตั€ะตะบะปัŽั‡ะธั‚ัŒ ั€ะตะถะธะผ ั†ะธะบะปะฐ.", - "missingQueuePerm": "ะขะพะปัŒะบะพ DJ ะธะปะธ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ั‹ ะผะพะณัƒั‚ ัƒะดะฐะปัั‚ัŒ ั‚ั€ะตะบะธ ะธะท ะพั‡ะตั€ะตะดะธ.", - "missingAutoPlayPerm": "ะขะพะปัŒะบะพ DJ ะธะปะธ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ั‹ ะผะพะณัƒั‚ ะฒะบะปัŽั‡ะฐั‚ัŒ ะธะปะธ ะฒั‹ะบะปัŽั‡ะฐั‚ัŒ ั€ะตะถะธะผ autoplay!", - "missingFunctionPerm": "ะขะพะปัŒะบะพ DJ ะธะปะธ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ั‹ ะผะพะณัƒั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัั‚ัƒ ั„ัƒะฝะบั†ะธัŽ.", - "timeFormatError": "ะะตะฒะตั€ะฝั‹ะน ั„ะพั€ะผะฐั‚ ะฒั€ะตะผะตะฝะธ. ะŸั€ะธะผะตั€: 2:42PM ะธะปะธ 12:39:31", - "lyricsNotFound": "ะขะตะบัั‚ ะฟะตัะฝะธ ะฝะต ะฝะฐะนะดะตะฝ. ะ’ะฒะตะดะธั‚ะต /lyrics <ะะฐะทะฒะฐะฝะธะต ะฟะตัะฝะธ> <ะะฒั‚ะพั€> ะดะปั ะฟะพะธัะบะฐ ั‚ะตะบัั‚ะฐ.", - "missingTrackInfo": "ะะตะบะพั‚ะพั€ะฐั ะธะฝั„ะพั€ะผะฐั†ะธั ะพ ั‚ั€ะตะบะต ะพั‚ััƒั‚ัั‚ะฒัƒะตั‚.", - "noVoiceChannel": "ะ“ะพะปะพัะพะฒะพะน ะบะฐะฝะฐะป ะฝะต ะฝะฐะนะดะตะฝ!", - "playlistAddError": "ะ’ะฐะผ ะฝะต ั€ะฐะทั€ะตัˆะตะฝะพ ะดะพะฑะฐะฒะปัั‚ัŒ ะฟะพั‚ะพะบะพะฒั‹ะต ะฒะธะดะตะพ ะฒ ะฟะปะตะนะปะธัั‚!", - "playlistAddError2": "ะŸั€ะพะธะทะพัˆะปะฐ ะพัˆะธะฑะบะฐ ะฟั€ะธ ะดะพะฑะฐะฒะปะตะฝะธะธ ั‚ั€ะตะบะพะฒ ะฒ ะฟะปะตะนะปะธัั‚!", - "playlistLimited": "ะ’ั‹ ะดะพัั‚ะธะณะปะธ ะปะธะผะธั‚ะฐ! ะ’ั‹ ะผะพะถะตั‚ะต ะดะพะฑะฐะฒะธั‚ัŒ ั‚ะพะปัŒะบะพ {0} ั‚ั€ะตะบะพะฒ ะฒ ัะฒะพะน ะฟะปะตะนะปะธัั‚.", - "playlistRepeated": "ะขะฐะบะพะน ั‚ั€ะตะบ ัƒะถะต ะตัั‚ัŒ ะฒ ะฟะปะตะนะปะธัั‚ะต!", - "playlistAdded": "โค๏ธ ะ”ะพะฑะฐะฒะปะตะฝ **{0}** ะฒ ะฟะปะตะนะปะธัั‚ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั {1} [`{2}`]!", - "playerDropdown": "ะ’ั‹ะฑะตั€ะธั‚ะต ั‚ั€ะตะบ ะดะปั ะฟะตั€ะตั…ะพะดะฐ ...", - "playerFilter": "ะ’ั‹ะฑะตั€ะธั‚ะต ั„ะธะปัŒั‚ั€ ะดะปั ะฟั€ะธะผะตะฝะตะฝะธั ...", - "buttonBack": "ะะฐะทะฐะด", - "buttonPause": "ะŸะฐัƒะทะฐ", - "buttonResume": "ะŸัƒัะบ", - "buttonSkip": "ะ’ะฟะตั€ะตะด", - "buttonLeave": "ะกะขะžะŸ", - "buttonLoop": "ะ—ะฐั†ะธะบะปะธั‚ัŒ", - "buttonVolumeUp": "ะ“ั€ะพะผะบะพัั‚ัŒ +", - "buttonVolumeDown": "ะ“ั€ะพะผะบะพัั‚ัŒ -", - "buttonVolumeMute": "ะ’ั‹ะบะปัŽั‡ะธั‚ัŒ ะทะฒัƒะบ", - "buttonVolumeUnmute": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะทะฒัƒะบ", - "buttonAutoPlay": "Autoplay", - "buttonShuffle": "ะŸะตั€ะตะผะตัˆะฐั‚ัŒ", - "buttonForward": "+10ัะตะบ", - "buttonRewind": "ะะฐะทะฐะด", - "buttonLyrics": "ะขะตะบัั‚ั‹ ะฟะตัะตะฝ", - "nowplayingDesc": "**ะกะตะนั‡ะฐั ะธะณั€ะฐะตั‚:**\n```{0}```", - "nowplayingField": "ะกะปะตะดัƒัŽั‰ะตะต:", - "nowplayingLink": "ะกะปัƒัˆะฐั‚ัŒ ะฝะฐ {0}", - "connect": "ะŸะพะดะบะปัŽั‡ะตะฝะพ ะบ {0}", - "live": "ะ’ ะญะคะ˜ะ ะ•", - "playlistLoad": "๐ŸŽถ ะ”ะพะฑะฐะฒะปะตะฝ ะฟะปะตะนะปะธัั‚ **{0}** ั `{1}` ั‚ั€ะตะบะฐะผะธ ะฒ ะพั‡ะตั€ะตะดัŒ.", - "trackLoad": "ะ”ะพะฑะฐะฒะปะตะฝ **[{0}](<{1}>)** ะพั‚ **{2}** (`{3}`) ะฒ ะพั‡ะตั€ะตะดัŒ.\n", - "trackLoad_pos": "ะ”ะพะฑะฐะฒะปะตะฝ **[{0}](<{1}>)** ะพั‚ **{2}** (`{3}`) ะฒ ะพั‡ะตั€ะตะดัŒ ะฝะฐ ะฟะพะทะธั†ะธัŽ **{4}**\n", - "searchTitle": "ะŸะพะธัะบ: {0}", - "searchDesc": "โžฅ ะŸะปะฐั‚ั„ะพั€ะผะฐ: {0} **{1}**\nโžฅ ะ ะตะทัƒะปัŒั‚ะฐั‚ั‹: **{2}**\n\n{3}", - "searchWait": "ะ’ั‹ะฑะตั€ะธั‚ะต ั‚ั€ะตะบ, ะบะพั‚ะพั€ั‹ะน ั…ะพั‚ะธั‚ะต ะดะพะฑะฐะฒะธั‚ัŒ ะฒ ะพั‡ะตั€ะตะดัŒ.", - "searchTimeout": "ะŸะพะธัะบ ะฟั€ะตั€ะฒะฐะฝ: ะฟั€ะตะฒั‹ัˆะตะฝะพ ะฒั€ะตะผั ะพะถะธะดะฐะฝะธั. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฟะพะฟั€ะพะฑัƒะนั‚ะต ะฟะพะทะถะต.", - "searchSuccess": "ะขั€ะตะบ ะดะพะฑะฐะฒะปะตะฝ ะฒ ะพั‡ะตั€ะตะดัŒ.", - "queueTitle": "ะกะปะตะดัƒัŽั‰ะธะน ะฒ ะพั‡ะตั€ะตะดะธ:", - "historyTitle": "ะ˜ัั‚ะพั€ะธั ะพั‡ะตั€ะตะดะธ:", - "viewTitle": "ะขะตะบัƒั‰ะฐั ะพั‡ะตั€ะตะดัŒ", - "viewDesc": "**ะกะตะนั‡ะฐั ะธะณั€ะฐะตั‚: [*ััั‹ะปะบะฐ*]({0}) โฎฏ**\n{1}", - "viewFooter": "ะกั‚ั€ะฐะฝะธั†ะฐ: {0}/{1} | ะžะฑั‰ะฐั ะฟั€ะพะดะพะปะถะธั‚ะตะปัŒะฝะพัั‚ัŒ: {2}", - "pauseError": "ะŸะปะตะตั€ ัƒะถะต ะฝะฐ ะฟะฐัƒะทะต.", - "pauseVote": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฟั€ะพะณะพะปะพัะพะฒะฐะป ะทะฐ ะฟะฐัƒะทัƒ ะฟะปะตะตั€ะฐ. [{1}/{2}]", - "paused": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฟะพัั‚ะฐะฒะธะป ะฟะปะตะตั€ ะฝะฐ ะฟะฐัƒะทัƒ.", - "resumeError": "ะŸะปะตะตั€ ะฝะต ะฝะฐ ะฟะฐัƒะทะต.", - "resumeVote": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฟั€ะพะณะพะปะพัะพะฒะฐะป ะทะฐ ะฟั€ะพะดะพะปะถะตะฝะธะต ั‚ั€ะตะบะฐ. [{1}/{2}]", - "resumed": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ัƒะฑั€ะฐะป ะฟะปะตะตั€ ั ะฟะฐัƒะทั‹.", - "shuffleError": "ะะตะดะพัั‚ะฐั‚ะพั‡ะฝะพ ั‚ั€ะตะบะพะฒ ะดะปั ะฟะตั€ะตะผะตัˆะธะฒะฐะฝะธั", - "shuffleVote": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฟั€ะพะณะพะปะพัะพะฒะฐะป ะทะฐ ะฟะตั€ะตะผะตัˆะธะฒะฐะฝะธะต ั‚ั€ะตะบะพะฒ. [{1}/{2}]", - "shuffled": "ะžั‡ะตั€ะตะดัŒ ะฟะตั€ะตะผะตัˆะฐะฝะฐ.", - "skipError": "ะะตั‚ ั‚ั€ะตะบะพะฒ ะดะปั ะฟั€ะพะฟัƒัะบะฐ.", - "skipVote": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฟั€ะพะณะพะปะพัะพะฒะฐะป ะทะฐ ะฟั€ะพะฟัƒัะบ ั‚ั€ะตะบะฐ. [{1}/{2}]", - "skipped": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฟั€ะพะฟัƒัั‚ะธะป ั‚ั€ะตะบ.", - "backVote": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฟั€ะพะณะพะปะพัะพะฒะฐะป ะทะฐ ะฒะพะทะฒั€ะฐั‚ ะบ ะฟั€ะตะดั‹ะดัƒั‰ะตะผัƒ ั‚ั€ะตะบัƒ. [{1}/{2}]", - "backed": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฒะตั€ะฝัƒะปัั ะบ ะฟั€ะตะดั‹ะดัƒั‰ะตะผัƒ ั‚ั€ะตะบัƒ.", - "leaveVote": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฟั€ะพะณะพะปะพัะพะฒะฐะป ะทะฐ ะพัั‚ะฐะฝะพะฒะบัƒ ะฟะปะตะตั€ะฐ. [{1}/{2}]", - "left": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะพัั‚ะฐะฝะพะฒะธะป ะฟะปะตะตั€.", - "seek": "ะŸะตั€ะตะผะพั‚ะฐั‚ัŒ ะฝะฐ **{0}**", - "repeat": "ะ ะตะถะธะผ ะฟะพะฒั‚ะพั€ะฐ `{0}`", - "cleared": "ะžั‡ะธั‰ะตะฝั‹ ะฒัะต ั‚ั€ะตะบะธ ะฒ `{0}`", - "removed": "ะฃะดะฐะปะตะฝะพ `{0}` ั‚ั€ะตะบะพะฒ ะธะท ะพั‡ะตั€ะตะดะธ.", - "forward": "ะŸะตั€ะตะผะพั‚ะฐั‚ัŒ ะฟะปะตะตั€ ะฒะฟะตั€ะตะด ะฝะฐ **{0}**", - "rewind": "ะžั‚ะผะพั‚ะฐั‚ัŒ ะฟะปะตะตั€ ะฝะฐะทะฐะด ะฝะฐ **{0}**", - "replay": "ะŸะพะฒั‚ะพั€ ั‚ะตะบัƒั‰ะตะณะพ ั‚ั€ะตะบะฐ.", - "swapped": "ะขั€ะตะบะธ `{0}` ะธ `{1}` ะฟะพะผะตะฝัะฝั‹ ะผะตัั‚ะฐะผะธ", - "moved": "ะขั€ะตะบ `{0}` ัะผะตั‰ั‘ะฝ ะฝะฐ `{1}`", - "autoplay": "ะ ะตะถะธะผ autoplay **{0}**", - "notdj": "ะ’ั‹ ะฝะต DJ. ะขะตะบัƒั‰ะธะน DJ: {0}.", - "djToMe": "ะ’ั‹ ะฝะต ะผะพะถะตั‚ะต ะฟะตั€ะตะดะฐั‚ัŒ ั€ะพะปัŒ DJ ัะตะฑะต ะธะปะธ ะฑะพั‚ัƒ.", - "djNotInChannel": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฝะต ะฝะฐั…ะพะดะธั‚ัั ะฒ ะณะพะปะพัะพะฒะพะผ ะบะฐะฝะฐะปะต.", - "djswap": "ะ’ั‹ ะฟะตั€ะตะดะฐะปะธ ั€ะพะปัŒ DJ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŽ `{0}`.", - "voicelinkQueueFull": "ะ˜ะทะฒะธะฝะธั‚ะต, ะฒั‹ ะดะพัั‚ะธะณะปะธ ะผะฐะบัะธะผะฐะปัŒะฝะพะณะพ ะบะพะปะธั‡ะตัั‚ะฒะฐ `{0}` ั‚ั€ะตะบะพะฒ ะฒ ะพั‡ะตั€ะตะดะธ!", - "voicelinkOutofList": "ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฟั€ะตะดะพัั‚ะฐะฒัŒั‚ะต ะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝั‹ะน ะธะฝะดะตะบั ั‚ั€ะตะบะฐ!", - "voicelinkDuplicateTrack": "ะ˜ะทะฒะธะฝะธั‚ะต, ัั‚ะพั‚ ั‚ั€ะตะบ ัƒะถะต ะตัั‚ัŒ ะฒ ะพั‡ะตั€ะตะดะธ.", - "decodeError": "ะงั‚ะพ-ั‚ะพ ะฟะพัˆะปะพ ะฝะต ั‚ะฐะบ ะฟั€ะธ ะดะตะบะพะดะธั€ะพะฒะฐะฝะธะธ ั„ะฐะนะปะฐ!", - "invalidStartTime": "ะะตะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝะพะต ะทะฝะฐั‡ะตะฝะธะต! ะ”ะธะฐะฟะพะทะพะฝ ะฒั€ะตะผะตะฝะธ ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ ะผะตะถะดัƒ `00:00` ะธ `{0}`.", - "invalidEndTime": "ะะตะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝะพะต ะทะฝะฐั‡ะตะฝะธะต! ะ”ะธะฐะฟะพะทะพะฝ ะฒั€ะตะผะตะฝะธ ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ ะผะตะถะดัƒ `00:00` ะธ `{0}`.", - "invalidTimeOrder": "ะ’ั€ะตะผั ะบะพะฝั†ะฐ ั‚ั€ะตะบะฐ ะฝะต ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ะผะตะฝัŒัˆะต ะธะปะธ ั€ะฐะฒะฝะพ ะฒั€ะตะผะตะฝะธ ะฝะฐั‡ะฐะปะฐ.", - "setStageAnnounceTemplate": "ะ“ะพั‚ะพะฒะพ! ะะพะฒั‹ะต ะฝะฐัั‚ั€ะพะนะบะธ ัั‚ะฐั‚ัƒัะฐ ะบะฐะฝะฐะปะฐ ัะบะพั€ะพ ะฟั€ะธะผะตะฝัั‚ัั ะฒ ัะพะพั‚ะฒะตั‚ัะฒะธะธ ั ะฒะฐัˆะธะผ ัˆะฐะฑะปะพะฝะพะผ.", - "createSongRequestChannel": "ะšะฐะฝะฐะป ะดะปั ะทะฐะฟั€ะพัะฐ ะฟะตัะตะฝ ({0}) ัะพะทะดะฐะฝ! ะ’ั‹ ะผะพะถะตั‚ะต ะทะฐะฟั€ะพัะธั‚ัŒ ะปัŽะฑัƒัŽ ะฟะตัะฝัŽ ะฟะพ ะฝะฐะทะฒะฐะฝะธัŽ ะธะปะธ URL ะฒ ัั‚ะพะผ ะบะฐะฝะฐะปะต ะฑะตะท ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั ะฟั€ะตั„ะธะบัะฐ ะฑะพั‚ะฐ." -} \ No newline at end of file + "common": { + "errors": { + "unknown": "โš ๏ธ ะงั‚ะพ-ั‚ะพ ะฟะพัˆะปะพ ะฝะต ั‚ะฐะบ ะฟั€ะธ ะฒั‹ะฟะพะปะฝะตะฝะธะธ ะบะพะผะฐะฝะดั‹! ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฟะพะฟั€ะพะฑัƒะนั‚ะต ะฟะพะทะถะต ะธะปะธ ะฟั€ะธัะพะตะดะธะฝะธั‚ะตััŒ ะบ ะฝะฐัˆะตะผัƒ ัะตั€ะฒะตั€ัƒ Discord ะดะปั ะฟะพะปัƒั‡ะตะฝะธั ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝะพะน ะฟะพะดะดะตั€ะถะบะธ.", + "missingIntents": "ะ˜ะทะฒะธะฝะธั‚ะต, ะฝะพ ัั‚ัƒ ะบะพะผะฐะฝะดัƒ ะฝะตะปัŒะทั ะฒั‹ะฟะพะปะฝะธั‚ัŒ, ั‚ะฐะบ ะบะฐะบ ัƒ ะฑะพั‚ะฐ ะพั‚ััƒั‚ัั‚ะฒัƒะตั‚ ะฝะตะพะฑั…ะพะดะธะผั‹ะน ะทะฐะฟั€ะพั ะฝะฐะผะตั€ะตะฝะธั: `({0})`." + }, + "status": { + "enabled": "ะฒะบะป", + "disabled": "ะฒั‹ะบะป", + "live": "ะ’ ะญะคะ˜ะ ะ•" + } + }, + "player": { + "errors": { + "nodeReconnect": "ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฟะพะฟั€ะพะฑัƒะนั‚ะต ั‡ัƒั‚ัŒ ะฟะพะทะถะต!", + "noPlaySource": "ะะต ะฟะพะปัƒั‡ะธะปะพััŒ ะฝะฐะนั‚ะธ ั€ะฐะฑะพั‡ะธะต ะธัั‚ะพั‡ะฝะธะบะธ!", + "noPlayer": "ะะฐ ัั‚ะพะผ ัะตั€ะฒะตั€ะต ะฝะต ะฝะฐะนะดะตะฝะพ ะฝะธ ะพะดะฝะพะณะพ ะฐะบั‚ะธะฒะฝะพะณะพ ะฟะปะตะตั€ะฐ.", + "noTrackPlaying": "ะกะตะนั‡ะฐั ะฝะตั‚ ะธะณั€ะฐัŽั‰ะธั… ั‚ั€ะตะบะพะฒ.", + "noTrackFound": "ะขั€ะตะบะธ ะฟะพ ั‚ะฐะบะพะผัƒ ะทะฐะฟั€ะพััƒ ะฝะต ะฝะฐะนะดะตะฝั‹! ะŸะพะถะฐะปัƒะนัั‚ะฐ, ัƒะบะฐะถะธั‚ะต ะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝัƒัŽ ััั‹ะปะบัƒ." + }, + "dropdown": { + "trackSelect": "ะ’ั‹ะฑะตั€ะธั‚ะต ั‚ั€ะตะบ ะดะปั ะฟะตั€ะตั…ะพะดะฐ ...", + "filterSelect": "ะ’ั‹ะฑะตั€ะธั‚ะต ั„ะธะปัŒั‚ั€ ะดะปั ะฟั€ะธะผะตะฝะตะฝะธั ..." + }, + "buttons": { + "back": "ะะฐะทะฐะด", + "pause": "ะŸะฐัƒะทะฐ", + "resume": "ะŸัƒัะบ", + "skip": "ะ’ะฟะตั€ะตะด", + "leave": "ะกะขะžะŸ", + "loop": "ะ—ะฐั†ะธะบะปะธั‚ัŒ", + "volumeUp": "ะ“ั€ะพะผะบะพัั‚ัŒ +", + "volumeDown": "ะ“ั€ะพะผะบะพัั‚ัŒ -", + "volumeMute": "ะ’ั‹ะบะปัŽั‡ะธั‚ัŒ ะทะฒัƒะบ", + "volumeUnmute": "ะ’ะบะปัŽั‡ะธั‚ัŒ ะทะฒัƒะบ", + "autoPlay": "Autoplay", + "shuffle": "ะŸะตั€ะตะผะตัˆะฐั‚ัŒ", + "forward": "+10ัะตะบ", + "rewind": "ะะฐะทะฐะด", + "lyrics": "ะขะตะบัั‚ั‹ ะฟะตัะตะฝ" + }, + "playback": { + "nowplayingDesc": "**ะกะตะนั‡ะฐั ะธะณั€ะฐะตั‚:**\n```{0}```", + "nowplayingField": "ะกะปะตะดัƒัŽั‰ะตะต:", + "nowplayingLink": "ะกะปัƒัˆะฐั‚ัŒ ะฝะฐ {0}", + "playlistLoad": "๐ŸŽถ ะ”ะพะฑะฐะฒะปะตะฝ ะฟะปะตะนะปะธัั‚ **{0}** ั `{1}` ั‚ั€ะตะบะฐะผะธ ะฒ ะพั‡ะตั€ะตะดัŒ.", + "trackLoad": "ะ”ะพะฑะฐะฒะปะตะฝ **[{0}](<{1}>)** ะพั‚ **{2}** (`{3}`) ะฒ ะพั‡ะตั€ะตะดัŒ.\n", + "trackLoadPos": "ะ”ะพะฑะฐะฒะปะตะฝ **[{0}](<{1}>)** ะพั‚ **{2}** (`{3}`) ะฒ ะพั‡ะตั€ะตะดัŒ ะฝะฐ ะฟะพะทะธั†ะธัŽ **{4}**\n" + }, + "controls": { + "pause": { + "error": "ะŸะปะตะตั€ ัƒะถะต ะฝะฐ ะฟะฐัƒะทะต.", + "vote": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฟั€ะพะณะพะปะพัะพะฒะฐะป ะทะฐ ะฟะฐัƒะทัƒ ะฟะปะตะตั€ะฐ. [{1}/{2}]", + "success": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฟะพัั‚ะฐะฒะธะป ะฟะปะตะตั€ ะฝะฐ ะฟะฐัƒะทัƒ." + }, + "resume": { + "error": "ะŸะปะตะตั€ ะฝะต ะฝะฐ ะฟะฐัƒะทะต.", + "vote": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฟั€ะพะณะพะปะพัะพะฒะฐะป ะทะฐ ะฟั€ะพะดะพะปะถะตะฝะธะต ั‚ั€ะตะบะฐ. [{1}/{2}]", + "success": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ัƒะฑั€ะฐะป ะฟะปะตะตั€ ั ะฟะฐัƒะทั‹." + }, + "shuffle": { + "error": "ะะตะดะพัั‚ะฐั‚ะพั‡ะฝะพ ั‚ั€ะตะบะพะฒ ะดะปั ะฟะตั€ะตะผะตัˆะธะฒะฐะฝะธั", + "vote": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฟั€ะพะณะพะปะพัะพะฒะฐะป ะทะฐ ะฟะตั€ะตะผะตัˆะธะฒะฐะฝะธะต ั‚ั€ะตะบะพะฒ. [{1}/{2}]", + "success": "ะžั‡ะตั€ะตะดัŒ ะฟะตั€ะตะผะตัˆะฐะฝะฐ." + }, + "skip": { + "error": "ะะตั‚ ั‚ั€ะตะบะพะฒ ะดะปั ะฟั€ะพะฟัƒัะบะฐ.", + "vote": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฟั€ะพะณะพะปะพัะพะฒะฐะป ะทะฐ ะฟั€ะพะฟัƒัะบ ั‚ั€ะตะบะฐ. [{1}/{2}]", + "success": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฟั€ะพะฟัƒัั‚ะธะป ั‚ั€ะตะบ." + }, + "back": { + "vote": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฟั€ะพะณะพะปะพัะพะฒะฐะป ะทะฐ ะฒะพะทะฒั€ะฐั‚ ะบ ะฟั€ะตะดั‹ะดัƒั‰ะตะผัƒ ั‚ั€ะตะบัƒ. [{1}/{2}]", + "success": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฒะตั€ะฝัƒะปัั ะบ ะฟั€ะตะดั‹ะดัƒั‰ะตะผัƒ ั‚ั€ะตะบัƒ." + }, + "leave": { + "vote": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฟั€ะพะณะพะปะพัะพะฒะฐะป ะทะฐ ะพัั‚ะฐะฝะพะฒะบัƒ ะฟะปะตะตั€ะฐ. [{1}/{2}]", + "success": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะพัั‚ะฐะฝะพะฒะธะป ะฟะปะตะตั€." + }, + "seek": "ะŸะตั€ะตะผะพั‚ะฐั‚ัŒ ะฝะฐ **{0}**", + "repeat": "ะ ะตะถะธะผ ะฟะพะฒั‚ะพั€ะฐ `{0}`", + "forward": "ะŸะตั€ะตะผะพั‚ะฐั‚ัŒ ะฟะปะตะตั€ ะฒะฟะตั€ะตะด ะฝะฐ **{0}**", + "rewind": "ะžั‚ะผะพั‚ะฐั‚ัŒ ะฟะปะตะตั€ ะฝะฐะทะฐะด ะฝะฐ **{0}**", + "replay": "ะŸะพะฒั‚ะพั€ ั‚ะตะบัƒั‰ะตะณะพ ั‚ั€ะตะบะฐ.", + "autoplay": "ะ ะตะถะธะผ autoplay **{0}**" + } + }, + "voice": { + "connection": { + "noChannel": "ะะตั‚ ะณะพะปะพัะพะฒะพะณะพ ะบะฐะฝะฐะปะฐ ะดะปั ะฟะพะดะบะปัŽั‡ะตะฝะธั. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ัƒะบะฐะถะธั‚ะต ะธะปะธ ะฟั€ะธัะพะตะดะธะฝะธั‚ะตััŒ ะบ ะพะดะฝะพะผัƒ.", + "alreadyConnected": "ะฃะถะต ะฟะพะดะบะปัŽั‡ะตะฝ ะบ ะณะพะปะพัะพะฒะพะผัƒ ะบะฐะฝะฐะปัƒ.", + "noPermission": "ะ˜ะทะฒะธะฝะธั‚ะต! ะฃ ะผะตะฝั ะฝะตั‚ ั€ะฐะทั€ะตัˆะตะฝะธั ะฝะฐ ะฟะพะดะบะปัŽั‡ะตะฝะธะต ะธะปะธ ั€ะฐะทะณะพะฒะพั€ ะฒ ะฒะฐัˆะตะผ ะณะพะปะพัะพะฒะพะผ ะบะฐะฝะฐะปะต.", + "notInChannel": "{0}, ะดะปั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั ะณะพะปะพัะพะฒั‹ั… ะบะพะผะฐะฝะด ะฒั‹ ะดะพะปะถะฝั‹ ะฝะฐั…ะพะดะธั‚ัŒัั ะฒ {1}. ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฟะตั€ะตะทะฐะนะดะธั‚ะต, ะตัะปะธ ะฒั‹ ัƒะถะต ะฝะฐั…ะพะดะธั‚ะตััŒ ะฒ ะณะพะปะพัะพะฒะพะผ ะบะฐะฝะฐะปะต!", + "connect": "ะŸะพะดะบะปัŽั‡ะตะฝะพ ะบ {0}" + }, + "stageChannel": { + "setAnnounceTemplate": "ะ“ะพั‚ะพะฒะพ! ะะพะฒั‹ะต ะฝะฐัั‚ั€ะพะนะบะธ ัั‚ะฐั‚ัƒัะฐ ะบะฐะฝะฐะปะฐ ัะบะพั€ะพ ะฟั€ะธะผะตะฝัั‚ัั ะฒ ัะพะพั‚ะฒะตั‚ัะฒะธะธ ั ะฒะฐัˆะธะผ ัˆะฐะฑะปะพะฝะพะผ." + } + }, + "permissions": { + "noCreatePermission": "ะ˜ะทะฒะธะฝะธั‚ะต! ะฃ ะผะตะฝั ะฝะตั‚ ะฟั€ะฐะฒ ะฝะฐ ัะพะทะดะฐะฝะธะต ะบะฐะฝะฐะปะฐ ะดะปั ะทะฐะฟั€ะพัะฐ ะฟะตัะตะฝ.", + "missingPosition": "ะขะพะปัŒะบะพ DJ ะธะปะธ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ั‹ ะผะพะณัƒั‚ ะธะทะผะตะฝัั‚ัŒ ะฟะพะทะธั†ะธัŽ.", + "missingMode": "ะขะพะปัŒะบะพ DJ ะธะปะธ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ั‹ ะผะพะณัƒั‚ ะฟะตั€ะตะบะปัŽั‡ะธั‚ัŒ ั€ะตะถะธะผ ั†ะธะบะปะฐ.", + "missingQueue": "ะขะพะปัŒะบะพ DJ ะธะปะธ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ั‹ ะผะพะณัƒั‚ ัƒะดะฐะปัั‚ัŒ ั‚ั€ะตะบะธ ะธะท ะพั‡ะตั€ะตะดะธ.", + "missingAutoPlay": "ะขะพะปัŒะบะพ DJ ะธะปะธ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ั‹ ะผะพะณัƒั‚ ะฒะบะปัŽั‡ะฐั‚ัŒ ะธะปะธ ะฒั‹ะบะปัŽั‡ะฐั‚ัŒ ั€ะตะถะธะผ autoplay!", + "missingFunction": "ะขะพะปัŒะบะพ DJ ะธะปะธ ะฐะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€ั‹ ะผะพะณัƒั‚ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ัั‚ัƒ ั„ัƒะฝะบั†ะธัŽ.", + "notDj": "ะ’ั‹ ะฝะต DJ. ะขะตะบัƒั‰ะธะน DJ: {0}.", + "djToSelf": "ะ’ั‹ ะฝะต ะผะพะถะตั‚ะต ะฟะตั€ะตะดะฐั‚ัŒ ั€ะพะปัŒ DJ ัะตะฑะต ะธะปะธ ะฑะพั‚ัƒ.", + "djNotInChannel": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฝะต ะฝะฐั…ะพะดะธั‚ัั ะฒ ะณะพะปะพัะพะฒะพะผ ะบะฐะฝะฐะปะต.", + "djSwapped": "ะ’ั‹ ะฟะตั€ะตะดะฐะปะธ ั€ะพะปัŒ DJ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŽ `{0}`." + }, + "settings": { + "actions": { + "languageNotFound": "ะฏะทั‹ะบะพะฒะพะน ะฟะฐะบะตั‚ ะฝะต ะฝะฐะนะดะตะฝ! ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฒั‹ะฑะตั€ะธั‚ะต ััƒั‰ะตัั‚ะฒัƒัŽั‰ะธะน ัะทั‹ะบะพะฒะพะน ะฟะฐะบะตั‚.", + "languageChanged": "ะฏะทั‹ะบ ัƒัะฟะตัˆะฝะพ ะธะทะผะตะฝะตะฝ ะฝะฐ `{0}`.", + "prefixSet": "ะ“ะพั‚ะพะฒะพ! ะœะพะน ะฟั€ะตั„ะธะบั ะฝะฐ ะฒะฐัˆะตะผ ัะตั€ะฒะตั€ะต ั‚ะตะฟะตั€ัŒ `{0}`. ะŸะพะฟั€ะพะฑัƒะนั‚ะต ะทะฐะฟัƒัั‚ะธั‚ัŒ `{1}ping`, ั‡ั‚ะพะฑั‹ ะฟั€ะพะฒะตั€ะธั‚ัŒ ะตะณะพ.", + "djSet": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ั€ะพะปัŒ DJ {0}.", + "queueModeSet": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ั€ะตะถะธะผ ะพั‡ะตั€ะตะดะธ ะฝะฐ `{0}`.", + "mode247": "ะขะตะฟะตั€ัŒ ัƒ ะฒะฐั `{0}` ั€ะตะถะธะผ 24/7.", + "bypassVote": "ะขะตะฟะตั€ัŒ ัƒ ะฒะฐั `{0}` ัะธัั‚ะตะผะฐ ะณะพะปะพัะพะฒะฐะฝะธั.", + "volumeSet": "ะฃัั‚ะฐะฝะพะฒะธั‚ัŒ ะณั€ะพะผะบะพัั‚ัŒ ะฝะฐ `{0}`%", + "controllerToggled": "ะขะตะฟะตั€ัŒ ัƒ ะฒะฐั `{0}` ะบะพะฝั‚ั€ะพะปะปะตั€ ะผัƒะทั‹ะบะธ.", + "duplicateTrackToggled": "ะขะตะฟะตั€ัŒ ัƒ ะฒะฐั `{0}` ั„ะธะปัŒั‚ั€ ะดัƒะฑะปะธะบะฐั‚ะพะฒ ะฒ ะพั‡ะตั€ะตะดะธ.", + "controllerMsgToggled": "ะขะตะฟะตั€ัŒ ัƒ ะฒะฐั `{0}` ัะพะพะฑั‰ะตะฝะธั ะพั‚ ะฟะปะตะตั€ะฐ.", + "silentMsgToggled": "ะฃ ะฒะฐั {0} ั‚ะธั…ะธั… ัะพะพะฑั‰ะตะฝะธะน." + }, + "menu": "ะะฐัั‚ั€ะพะนะบะธ ัะตั€ะฒะตั€ะฐ | {0}", + "basic": { + "title": "โค๏ธ ะžัะฝะพะฒะฝะฐั ะธะฝั„ะพั€ะผะฐั†ะธั:", + "value": "```ะŸั€ะตั„ะธะบั: {0}\nะฏะทั‹ะบ: {1}\nะ’ะบะปัŽั‡ะธั‚ัŒ ะบะพะฝั‚ั€ะพะปะปะตั€ ะผัƒะทั‹ะบะธ: {2}\nDJ ั€ะพะปัŒ: @{3}\nะžะฑั…ะพะด ะณะพะปะพัะพะฒะฐะฝะธั: {4}\n24/7: {5}\nะ“ั€ะพะผะบะพัั‚ัŒ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ: {6}%\nะ’ั€ะตะผั ะฟั€ะพะธะณั€ั‹ะฒะฐะฝะธั: {7}```" + }, + "queue": { + "title": "๐Ÿ”— ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพะฑ ะพั‡ะตั€ะตะดะธ:", + "value": "```ะ ะตะถะธะผ ะพั‡ะตั€ะตะดะธ: {0}\nะœะฐะบัะธะผะฐะปัŒะฝะพะต ะบะพะปะธั‡ะตัั‚ะฒะพ ะฟะตัะตะฝ: {1}\nะ ะฐะทั€ะตัˆะธั‚ัŒ ะดัƒะฑะปะธั€ะพะฒะฐะฝะธะต ั‚ั€ะตะบะพะฒ: {2}```" + }, + "voice": { + "title": "๐ŸŽค ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ัะพัั‚ะพัะฝะธะธ ะณะพะปะพัะฐ:" + }, + "permissions": { + "title": "โœจ ะŸั€ะฐะฒะฐ:", + "value": "```{0} ะะดะผะธะฝะธัั‚ั€ะฐั‚ะพั€\n{1} ะฃะฟั€ะฐะฒะปะตะฝะธะต_ะกะตั€ะฒะตั€ะพะผ\n{2} ะฃะฟั€ะฐะฒะปะตะฝะธะต_ะšะฐะฝะฐะปะพะผ\n{3} ะฃะฟั€ะฐะฒะปะตะฝะธะต_ะกะพะพะฑั‰ะตะฝะธัะผะธ```" + } + }, + "ping": { + "title1": "ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ะฑะพั‚ะต:", + "title2": "ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ะฟะปะตะตั€ะต:", + "field1": "```ID ะจะฐั€ะดะฐ: {0}/{1}\nะ—ะฐะดะตั€ะถะบะฐ ะจะฐั€ะดะฐ: {2:.3f}s {3}\nะ ะตะณะธะพะฝ: {4}```", + "field2": "```ะกะตั€ะฒะธั: {0} - {1:.3f}s\nะ˜ะณั€ะพะบะธ: {2}\nะ ะตะณะธะพะฝ ะ“ะพะปะพัะฐ: {3}```" + }, + "effects": { + "added": "ะŸั€ะธะผะตะฝะตะฝ `{0}` ัั„ั„ะตะบั‚.", + "cleared": "ะ—ะฒัƒะบะพะฒั‹ะต ัั„ั„ะตะบั‚ั‹ ะฑั‹ะปะธ ะพั‡ะธั‰ะตะฝั‹!", + "tagInUse": "ะญั‚ะพั‚ ะทะฒัƒะบะพะฒะพะน ัั„ั„ะตะบั‚ ัƒะถะต ะธัะฟะพะปัŒะทัƒะตั‚ัั! ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะธัะฟะพะปัŒะทัƒะนั‚ะต /cleareffect <ะขะตะณ>, ั‡ั‚ะพะฑั‹ ัƒะดะฐะปะธั‚ัŒ ะตะณะพ." + }, + "playlist": { + "view": { + "title": "ะ’ัะต ะฟะปะตะนะปะธัั‚ั‹ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั {0}", + "headers": "ID:,ะ’ั€ะตะผั:,ะะฐะทะฒะฐะฝะธะต:,ะขั€ะตะบะธ:", + "footer": "ะ’ะฒะตะดะธั‚ะต /playlist play [ะฟะปะตะนะปะธัั‚], ั‡ั‚ะพะฑั‹ ะดะพะฑะฐะฒะธั‚ัŒ ะฟะปะตะนะปะธัั‚ ะฒ ะพั‡ะตั€ะตะดัŒ.", + "detailTitle": "ะŸั€ะพัะผะพั‚ั€ ะฟะปะตะนะปะธัั‚ะพะฒ", + "detailDesc": "```ะ˜ะผั: {0} [{1}]\nะ’ัะตะณะพ ั‚ั€ะตะบะพะฒ: {2}\nะ’ะปะฐะดะตะปะตั†: {3}\nะขะธะฟ: {4}\n```", + "permsValue": "๐Ÿ“– ะงั‚ะตะฝะธะต: โœ“ โœ๐Ÿฝ ะ—ะฐะฟะธััŒ: {0} ๐Ÿ—‘๏ธ ะฃะดะฐะปะตะฝะธะต: {1}", + "permsValue2": "๐Ÿ“– ะงั‚ะตะฝะธะต: {0}", + "trackList": "ะขั€ะตะบะธ", + "footer2": "ะžะฑั‰ะฐั ะฟั€ะพะดะพะปะถะธั‚ะตะปัŒะฝะพัั‚ัŒ: {0}", + "playlistSelect": "ะ’ั‹ะฑะตั€ะธั‚ะต ะฟะปะตะนะปะธัั‚ ะดะปั ะฟั€ะพัะผะพั‚ั€ะฐ ..." + }, + "errors": { + "notFound": "ะŸะปะตะนะปะธัั‚ [`{0}`] ะฝะต ะฝะฐะนะดะตะฝ. ะ’ะฒะตะดะธั‚ะต /playlist view, ั‡ั‚ะพะฑั‹ ะฟะพัะผะพั‚ั€ะตั‚ัŒ ะฒัะต ะฒะฐัˆะธ ะฟะปะตะนะปะธัั‚ั‹.", + "noAccess": "ะ˜ะทะฒะธะฝะธั‚ะต! ะฃ ะฒะฐั ะฝะตั‚ ะดะพัั‚ัƒะฟะฐ ะบ ัั‚ะพะผัƒ ะฟะปะตะนะปะธัั‚ัƒ!", + "noTrack": "ะ˜ะทะฒะธะฝะธั‚ะต! ะ’ ะฟะปะตะนะปะธัั‚ะต [`{0}`] ะฝะตั‚ ั‚ั€ะตะบะพะฒ.", + "notAllowed": "ะญั‚ะฐ ะบะพะผะฐะฝะดะฐ ะฝะต ั€ะฐะทั€ะตัˆะตะฝะฐ ะดะปั ะพะฑั‰ะธั… ะฟะปะตะนะปะธัั‚ะพะฒ.", + "nameOverLimit": "ะ˜ะทะฒะธะฝะธั‚ะต! ะ˜ะผั ะฟะปะตะนะปะธัั‚ะฐ ะฝะต ะผะพะถะตั‚ ะฟั€ะตะฒั‹ัˆะฐั‚ัŒ 10 ัะธะผะฒะพะปะพะฒ.", + "sameName": "ะ˜ะทะฒะธะฝะธั‚ะต! ะญั‚ะพ ะธะผั ะฝะต ะผะพะถะตั‚ ัะพะฒะฟะฐะดะฐั‚ัŒ ั ะฒะฐัˆะธะผ ะฝะพะฒั‹ะผ ะธะผะตะฝะตะผ.", + "deleteDefault": "ะ’ั‹ ะฝะต ะผะพะถะตั‚ะต ัƒะดะฐะปะธั‚ัŒ ะฟะปะตะนะปะธัั‚ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ.", + "limitReached": "ะ’ั‹ ะฝะต ะผะพะถะตั‚ะต ัะพะทะดะฐะฒะฐั‚ัŒ ะฑะพะปัŒัˆะต `{0}` ะฟะปะตะนะปะธัั‚ะพะฒ!", + "exists": "ะŸะปะตะนะปะธัั‚ [`{0}`] ัƒะถะต ััƒั‰ะตัั‚ะฒัƒะตั‚.", + "invalidUrl": "ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฒะฒะตะดะธั‚ะต ะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝัƒัŽ ััั‹ะปะบัƒ ะธะปะธ ะฟัƒะฑะปะธั‡ะฝัƒัŽ ะฟะปะตะนะปะธัั‚-ััั‹ะปะบัƒ ะฝะฐ Spotify ะธะปะธ YouTube.", + "trackLimitReached": "ะ’ั‹ ะดะพัั‚ะธะณะปะธ ะปะธะผะธั‚ะฐ! ะ’ั‹ ะผะพะถะตั‚ะต ะดะพะฑะฐะฒะธั‚ัŒ ั‚ะพะปัŒะบะพ `{0}` ั‚ั€ะตะบะพะฒ ะฒ ัะฒะพะน ะฟะปะตะนะปะธัั‚.", + "playlistLinkNotAllowed": "ะ’ะฐะผ ะฝะต ั€ะฐะทั€ะตัˆะตะฝะพ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ััั‹ะปะบัƒ ะฝะฐ ะฟะปะตะนะปะธัั‚.", + "streamNotAllowed": "ะ’ะฐะผ ะฝะต ั€ะฐะทั€ะตัˆะตะฝะพ ะดะพะฑะฐะฒะปัั‚ัŒ ะฟะพั‚ะพะบะพะฒั‹ะต ะฒะธะดะตะพ ะฒ ัะฒะพะน ะฟะปะตะนะปะธัั‚.", + "positionNotFound": "ะะต ะฟะพะปัƒั‡ะธะปะพััŒ ะฝะฐะนั‚ะธ ะฟะพะทะธั†ะธัŽ `{0}` ะฒ ะฒะฐัˆะตะผ ะฟะปะตะนะปะธัั‚ะต [`{1}`]!", + "streaming": "ะ’ะฐะผ ะฝะต ั€ะฐะทั€ะตัˆะตะฝะพ ะดะพะฑะฐะฒะปัั‚ัŒ ะฟะพั‚ะพะบะพะฒั‹ะต ะฒะธะดะตะพ ะฒ ะฟะปะตะนะปะธัั‚!", + "track": "ะŸั€ะพะธะทะพัˆะปะฐ ะพัˆะธะฑะบะฐ ะฟั€ะธ ะดะพะฑะฐะฒะปะตะฝะธะธ ั‚ั€ะตะบะพะฒ ะฒ ะฟะปะตะนะปะธัั‚!", + "trackLimited": "ะ’ั‹ ะดะพัั‚ะธะณะปะธ ะปะธะผะธั‚ะฐ! ะ’ั‹ ะผะพะถะตั‚ะต ะดะพะฑะฐะฒะธั‚ัŒ ั‚ะพะปัŒะบะพ {0} ั‚ั€ะตะบะพะฒ ะฒ ัะฒะพะน ะฟะปะตะนะปะธัั‚.", + "trackRepeated": "ะขะฐะบะพะน ั‚ั€ะตะบ ัƒะถะต ะตัั‚ัŒ ะฒ ะฟะปะตะนะปะธัั‚ะต!" + }, + "actions": { + "play": "ะ”ะพะฑะฐะฒะปะตะฝ ะฟะปะตะนะปะธัั‚ [`{0}`] ั `{1}` ั‚ั€ะตะบะฐะผะธ ะฒ ะพั‡ะตั€ะตะดัŒ.", + "removed": "ะ’ั‹ ัƒะดะฐะปะธะปะธ ะฟะปะตะนะปะธัั‚ [`{0}`].", + "created": "ะ’ั‹ ัะพะทะดะฐะปะธ ะฟะปะตะนะปะธัั‚ `{0}`. ะ’ะฒะตะดะธั‚ะต /playlist view ะดะปั ะฟะพะปัƒั‡ะตะฝะธั ะดะพะฟะพะปะฝะธั‚ะตะปัŒะฝะพะน ะธะฝั„ะพั€ะผะฐั†ะธะธ.", + "renamed": "ะ’ั‹ ะฟะตั€ะตะธะผะตะฝะพะฒะฐะปะธ `{0}` ะฒ `{1}`.", + "trackRemoved": "๐Ÿ‘‹ ะฃะดะฐะปะตะฝ **{0}** ะธะท ะฟะปะตะนะปะธัั‚ะฐ {1} [`{2}`].", + "cleared": "ะ’ั‹ ัƒัะฟะตัˆะฝะพ ะพั‡ะธัั‚ะธะปะธ ัะฒะพะน ะฟะปะตะนะปะธัั‚ [`{0}`].", + "trackAdded": "โค๏ธ ะ”ะพะฑะฐะฒะปะตะฝ **{0}** ะฒ ะฟะปะตะนะปะธัั‚ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั {1} [`{2}`]!" + }, + "sharing": { + "sendErrorPlayer": "ะ˜ะทะฒะธะฝะธั‚ะต! ะ’ั‹ ะฝะต ะผะพะถะตั‚ะต ะพั‚ะฟั€ะฐะฒะธั‚ัŒ ะฟั€ะธะณะปะฐัˆะตะฝะธะต ัะฐะผะพะผัƒ ัะตะฑะต.", + "sendErrorBot": "ะ˜ะทะฒะธะฝะธั‚ะต! ะ’ั‹ ะฝะต ะผะพะถะตั‚ะต ะพั‚ะฟั€ะฐะฒะธั‚ัŒ ะฟั€ะธะณะปะฐัˆะตะฝะธะต ะฑะพั‚ัƒ.", + "belongs": "ะ˜ะทะฒะธะฝะธั‚ะต! ะญั‚ะพั‚ ะฟะปะตะนะปะธัั‚ ะฟั€ะธะฝะฐะดะปะตะถะธั‚ <@{0}>.", + "alreadyShared": "ะ˜ะทะฒะธะฝะธั‚ะต! ะญั‚ะธะผ ะฟะปะตะนะปะธัั‚ะพะผ ัƒะถะต ะฟะพะดะตะปะธะปะธััŒ ั {0}.", + "alreadySent": "ะ˜ะทะฒะธะฝะธั‚ะต! ะ’ั‹ ัƒะถะต ะพั‚ะฟั€ะฐะฒะธะปะธ ะฟั€ะธะณะปะฐัˆะตะฝะธะต ั€ะฐะฝะตะต.", + "noAccount": "ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒ `{0}` ะฝะต ัะพะทะดะฐะป ัƒั‡ะตั‚ะฝัƒัŽ ะทะฐะฟะธััŒ ะฟะปะตะนะปะธัั‚ะฐ.", + "invitationSent": "ะŸั€ะธะณะปะฐัˆะตะฝะธะต ะพั‚ะฟั€ะฐะฒะปะตะฝะพ {0}." + }, + "inbox": { + "full": "ะ˜ะทะฒะธะฝะธั‚ะต! ะŸะพั‡ั‚ะพะฒั‹ะน ัั‰ะธะบ {0} ะฟะตั€ะตะฟะพะปะฝะตะฝ.", + "noMessages": "ะ’ ะฒะฐัˆะตะผ ะฟะพั‡ั‚ะพะฒะพะผ ัั‰ะธะบะต ะฝะตั‚ ัะพะพะฑั‰ะตะฝะธะน." + } + }, + "search": { + "noLinkSupport": "ะšะพะผะฐะฝะดะฐ ะฟะพะธัะบะฐ ะฝะต ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ััั‹ะปะบะธ!", + "title": "ะŸะพะธัะบ: {0}", + "desc": "โžฅ ะŸะปะฐั‚ั„ะพั€ะผะฐ: {0} **{1}**\nโžฅ ะ ะตะทัƒะปัŒั‚ะฐั‚ั‹: **{2}**\n\n{3}", + "wait": "ะ’ั‹ะฑะตั€ะธั‚ะต ั‚ั€ะตะบ, ะบะพั‚ะพั€ั‹ะน ั…ะพั‚ะธั‚ะต ะดะพะฑะฐะฒะธั‚ัŒ ะฒ ะพั‡ะตั€ะตะดัŒ.", + "success": "ะขั€ะตะบ ะดะพะฑะฐะฒะปะตะฝ ะฒ ะพั‡ะตั€ะตะดัŒ." + }, + "voting": { + "voted": "ะ’ั‹ ะฟั€ะพะณะพะปะพัะพะฒะฐะปะธ!" + }, + "time": { + "formatError": "ะะตะฒะตั€ะฝั‹ะน ั„ะพั€ะผะฐั‚ ะฒั€ะตะผะตะฝะธ. ะŸั€ะธะผะตั€: 2:42PM ะธะปะธ 12:39:31", + "invalidStartTime": "ะะตะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝะพะต ะทะฝะฐั‡ะตะฝะธะต! ะ”ะธะฐะฟะพะทะพะฝ ะฒั€ะตะผะตะฝะธ ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ ะผะตะถะดัƒ `00:00` ะธ `{0}`.", + "invalidEndTime": "ะะตะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝะพะต ะทะฝะฐั‡ะตะฝะธะต! ะ”ะธะฐะฟะพะทะพะฝ ะฒั€ะตะผะตะฝะธ ะดะพะปะถะตะฝ ะฑั‹ั‚ัŒ ะผะตะถะดัƒ `00:00` ะธ `{0}`." + }, + "lyrics": { + "notFound": "ะขะตะบัั‚ ะฟะตัะฝะธ ะฝะต ะฝะฐะนะดะตะฝ. ะ’ะฒะตะดะธั‚ะต /lyrics <ะะฐะทะฒะฐะฝะธะต ะฟะตัะฝะธ> <ะะฒั‚ะพั€> ะดะปั ะฟะพะธัะบะฐ ั‚ะตะบัั‚ะฐ." + }, + "queue": { + "management": { + "title": "ะกะปะตะดัƒัŽั‰ะธะน ะฒ ะพั‡ะตั€ะตะดะธ:", + "cleared": "ะžั‡ะธั‰ะตะฝั‹ ะฒัะต ั‚ั€ะตะบะธ ะฒ `{0}`", + "removed": "ะฃะดะฐะปะตะฝะพ `{0}` ั‚ั€ะตะบะพะฒ ะธะท ะพั‡ะตั€ะตะดะธ.", + "swapped": "ะขั€ะตะบะธ `{0}` ะธ `{1}` ะฟะพะผะตะฝัะฝั‹ ะผะตัั‚ะฐะผะธ", + "moved": "ะขั€ะตะบ `{0}` ัะผะตั‰ั‘ะฝ ะฝะฐ `{1}`" + }, + "view": { + "historyTitle": "ะ˜ัั‚ะพั€ะธั ะพั‡ะตั€ะตะดะธ:", + "title": "ะขะตะบัƒั‰ะฐั ะพั‡ะตั€ะตะดัŒ", + "desc": "**ะกะตะนั‡ะฐั ะธะณั€ะฐะตั‚: [*ััั‹ะปะบะฐ*]({0}) โฎฏ**\n{1}" + }, + "errors": { + "queueFull": "ะ˜ะทะฒะธะฝะธั‚ะต, ะฒั‹ ะดะพัั‚ะธะณะปะธ ะผะฐะบัะธะผะฐะปัŒะฝะพะณะพ ะบะพะปะธั‡ะตัั‚ะฒะฐ `{0}` ั‚ั€ะตะบะพะฒ ะฒ ะพั‡ะตั€ะตะดะธ!", + "outOfList": "ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฟั€ะตะดะพัั‚ะฐะฒัŒั‚ะต ะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝั‹ะน ะธะฝะดะตะบั ั‚ั€ะตะบะฐ!", + "duplicateTrack": "ะ˜ะทะฒะธะฝะธั‚ะต, ัั‚ะพั‚ ั‚ั€ะตะบ ัƒะถะต ะตัั‚ัŒ ะฒ ะพั‡ะตั€ะตะดะธ." + } + }, + "songRequest": { + "channelCreated": "ะšะฐะฝะฐะป ะดะปั ะทะฐะฟั€ะพัะฐ ะฟะตัะตะฝ ({0}) ัะพะทะดะฐะฝ! ะ’ั‹ ะผะพะถะตั‚ะต ะทะฐะฟั€ะพัะธั‚ัŒ ะปัŽะฑัƒัŽ ะฟะตัะฝัŽ ะฟะพ ะฝะฐะทะฒะฐะฝะธัŽ ะธะปะธ URL ะฒ ัั‚ะพะผ ะบะฐะฝะฐะปะต ะฑะตะท ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั ะฟั€ะตั„ะธะบัะฐ ะฑะพั‚ะฐ." + }, + "pagination": { + "prev": "ะะฐะทะฐะด", + "next": "ะ’ะฟะตั€ะตะด", + "play": "ะ˜ะณั€ะฐั‚ัŒ", + "share": "ะŸะพะดะตะปะธั‚ัŒัั", + "export": "ะญะบัะฟะพั€ั‚ะธั€ะพะฒะฐั‚ัŒ", + "delete": "ะฃะดะฐะปะธั‚ัŒ", + "page": { + "title": "ะ’ะฒะตะดะธั‚ะต ะฝะพะผะตั€ ัั‚ั€ะฐะฝะธั†ั‹", + "field": "ะะพะผะตั€ ัั‚ั€ะฐะฝะธั†ั‹" + } + } +} diff --git a/langs/UA.json b/langs/UA.json index cecbf4b..919b0c6 100644 --- a/langs/UA.json +++ b/langs/UA.json @@ -1,172 +1,262 @@ { - "unknownException": "โš ๏ธ ะฉะพััŒ ะฟั–ัˆะปะพ ะฝะต ั‚ะฐะบ ะฟั–ะด ั‡ะฐั ะฒะธะบะพะฝะฐะฝะฝั ะบะพะผะฐะฝะดะธ! ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ัะฟั€ะพะฑัƒะนั‚ะต ะฟั–ะทะฝั–ัˆะต ะฐะฑะพ ะฟั€ะธั”ะดะฝะฐะนั‚ะตัั ะดะพ ะฝะฐัˆะพะณะพ ัะตั€ะฒะตั€ะฐ Discord ะดะปั ะพั‚ั€ะธะผะฐะฝะฝั ะดะพะดะฐั‚ะบะพะฒะพั— ะฟั–ะดั‚ั€ะธะผะบะธ.", - "enabled": "ะฒะบะป", - "disabled": "ะฒะธะบะป", - "nodeReconnect": "ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ัะฟั€ะพะฑัƒะนั‚ะต ะทะฝะพะฒัƒ! ะŸั–ัะปั ะฟะตั€ะตะฟั–ะดะบะปัŽั‡ะตะฝะฝั ะฒัƒะทะปะฐ.", - "noChannel": "ะะตะผะฐั” ะณะพะปะพัะพะฒะพะณะพ ะบะฐะฝะฐะปัƒ ะดะปั ะฟั–ะดะบะปัŽั‡ะตะฝะฝั. ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฒะบะฐะถั–ั‚ัŒ ะฐะฑะพ ะฟั€ะธั”ะดะฝะฐะนั‚ะตัั ะดะพ ะพะดะฝะพะณะพ.", - "alreadyConnected": "ะฃะถะต ะฟั–ะดะบะปัŽั‡ะตะฝะธะน ะดะพ ะณะพะปะพัะพะฒะพะณะพ ะบะฐะฝะฐะปัƒ.", - "noPermission": "ะ’ะธะฑะฐั‡ั‚ะต! ะฃ ะผะตะฝะต ะฝะตะผะฐั” ะดะพะทะฒะพะปัƒ ะฝะฐ ะฟั–ะดะบะปัŽั‡ะตะฝะฝั ะฐะฑะพ ั€ะพะทะผะพะฒัƒ ัƒ ะฒะฐัˆะพะผัƒ ะณะพะปะพัะพะฒะพะผัƒ ะบะฐะฝะฐะปั–.", - "noCreatePermission": "ะ’ะธะฑะฐั‡ั‚ะต! ะฃ ะผะตะฝะต ะฝะตะผะฐั” ะฟั€ะฐะฒ ะดะปั ัั‚ะฒะพั€ะตะฝะฝั ะบะฐะฝะฐะปัƒ ะทะฐะฟะธั‚ั–ะฒ ะฝะฐ ะฟั–ัะฝั–.", - "noPlaySource": "ะะตะผะพะถะปะธะฒะพ ะทะฝะฐะนั‚ะธ ั€ะพะฑะพั‡ั– ะดะถะตั€ะตะปะฐ!", - "noPlayer": "ะะฐ ั†ัŒะพะผัƒ ัะตั€ะฒะตั€ั– ะฝะต ะทะฝะฐะนะดะตะฝะพ ะถะพะดะฝะพะณะพ ะฐะบั‚ะธะฒะฝะพะณะพ ะฟะปะตั”ั€ะฐ.", - "notVote": "ะฆั ะบะพะผะฐะฝะดะฐ ะฒะธะผะฐะณะฐั” ะฒะฐัˆะพะณะพ ะณะพะปะพััƒ! ะ’ะฒะตะดั–ั‚ัŒ `/vote` ะดะปั ะพั‚ั€ะธะผะฐะฝะฝั ะดะพะดะฐั‚ะบะพะฒะพั— ั–ะฝั„ะพั€ะผะฐั†ั–ั—.", - "missingIntents": "ะ’ะธะฑะฐั‡ั‚ะต, ะฐะปะต ั†ัŽ ะบะพะผะฐะฝะดัƒ ะฝะต ะผะพะถะฝะฐ ะฒะธะบะพะฝะฐั‚ะธ, ะพัะบั–ะปัŒะบะธ ัƒ ะฑะพั‚ะฐ ะฒั–ะดััƒั‚ะฝั–ะน ะฝะตะพะฑั…ั–ะดะฝะธะน ะทะฐะฟะธั‚ ะฝะฐ ั–ะฝั‚ะตะฝั†ั–ัŽ: `({0})`.", - "languageNotFound": "ะœะพะฒะฝะธะน ะฟะฐะบะตั‚ ะฝะต ะทะฝะฐะนะดะตะฝะพ! ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฒะธะฑะตั€ั–ั‚ัŒ ะฝะฐัะฒะฝะธะน ะผะพะฒะฝะธะน ะฟะฐะบะตั‚.", - "changedLanguage": "ะฃัะฟั–ัˆะฝะพ ะทะผั–ะฝะตะฝะพ ะฝะฐ ะผะพะฒะฝะธะน ะฟะฐะบะตั‚ `{0}`.", - "setPrefix": "ะ“ะพั‚ะพะฒะพ! ะœั–ะน ะฟั€ะตั„ั–ะบั ะฝะฐ ะฒะฐัˆะพะผัƒ ัะตั€ะฒะตั€ั– ั‚ะตะฟะตั€ `{0}`. ะกะฟั€ะพะฑัƒะนั‚ะต ะทะฐะฟัƒัั‚ะธั‚ะธ `{1}ping`, ั‰ะพะฑ ะฟะตั€ะตะฒั–ั€ะธั‚ะธ ะนะพะณะพ.", - "setDJ": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ั€ะพะปัŒ DJ {0}.", - "setQueue": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ั€ะตะถะธะผ ั‡ะตั€ะณะธ ะฝะฐ `{0}`.", - "247": "ะขะตะฟะตั€ ัƒ ะฒะฐั `{0}` ั€ะตะถะธะผ 24/7.", - "bypassVote": "ะขะตะฟะตั€ ัƒ ะฒะฐั `{0}` ัะธัั‚ะตะผะฐ ะณะพะปะพััƒะฒะฐะฝะฝั.", - "setVolume": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ะณัƒั‡ะฝั–ัั‚ัŒ ะฝะฐ `{0}`%", - "toggleController": "ะขะตะฟะตั€ ัƒ ะฒะฐั `{0}` ะบะพะฝั‚ั€ะพะปะตั€ ะผัƒะทะธะบะธ.", - "toggleDuplicateTrack": "ะขะตะฟะตั€ ัƒ ะฒะฐั `{0}` ะทะฐะฟะพะฑั–ะณะฐะฝะฝั ะดัƒะฑะปัŽะฒะฐะฝะฝัŽ ั‚ั€ะตะบัƒ ะฒ ั‡ะตั€ะทั–.", - "toggleControllerMsg": "ะขะตะฟะตั€ ัƒ ะฒะฐั `{0}` ะฟะพะฒั–ะดะพะผะปะตะฝะฝั ะฒั–ะด ะบะพะฝั‚ั€ะพะปะตั€ะฐ ะผัƒะทะธะบะธ.", - "toggleSilentMsg": "ะฃ ะฒะฐั {0} ั‚ะธั…ะธั… ะฟะพะฒั–ะดะพะผะปะตะฝัŒ.", - "settingsMenu": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ัะตั€ะฒะตั€ะฐ | {0}", - "settingsTitle": "โค๏ธ ะžัะฝะพะฒะฝะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ั:", - "settingsValue": "```ะŸั€ะตั„ั–ะบั: {0}\nะœะพะฒะฐ: {1}\nะฃะฒั–ะผะบะฝัƒั‚ะธ ะบะพะฝั‚ั€ะพะปะตั€ ะผัƒะทะธะบะธ: {2}\nDJ ั€ะพะปัŒ: {3}\nะžะฑั…ั–ะด ะณะพะปะพััƒะฒะฐะฝะฝั: {4}\n24/7: {5}\nะ“ั€ะพะผะบั–ัั‚ัŒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ: {6}%\nะงะฐั ะฟั€ะพะณั€ะฐะฒะฐะฝะฝั: {7}```", - "settingsTitle2": "๐Ÿ”— ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ั‡ะตั€ะณัƒ:", - "settingsValue2": "```ะ ะตะถะธะผ ั‡ะตั€ะณะธ: {0}\nะœะฐะบัะธะผะฐะปัŒะฝะฐ ะบั–ะปัŒะบั–ัั‚ัŒ ะฟั–ัะตะฝัŒ: {1}\nะ”ะพะทะฒะพะปะธั‚ะธ ะดัƒะฑะปัŽะฒะฐะฝะฝั ั‚ั€ะตะบั–ะฒ: {2}```", - "settingsTitle3": "๐ŸŽค ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ัั‚ะฐะฝ ะณะพะปะพััƒ:", - "settingsPermTitle": "โœจ ะŸั€ะฐะฒะฐ:", - "settingsPermValue": "```{0} ะะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€\n{1} ะšะตั€ัƒะฒะฐะฝะฝั_ะกะตั€ะฒะตั€ะพะผ\n{2} ะšะตั€ัƒะฒะฐะฝะฝั_ะšะฐะฝะฐะปะพะผ\n{3} ะšะตั€ัƒะฒะฐะฝะฝั_ะกะพะพะฑั‰ะตะฝะธัะผะธ```", - "pingTitle1": "ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ะฑะพั‚ะฐ:", - "pingTitle2": "ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ะฟะปะตั”ั€:", - "pingField1": "````ID ะจะฐั€ะดะฐ: {0}/{1}\nะ—ะฐั‚ั€ะธะผะบะฐ ะจะฐั€ะดะฐ: {2:.3f}s {3}\nะ ะตะณั–ะพะฝ: {4}````", - "pingField2": "```ะ’ัƒะทะพะป: {0} - {1:.3f}s\nะ“ั€ะฐะฒั†ั–: {2}\nะ ะตะณั–ะพะฝ ะ“ะพะปะพััƒ: {3}```", - "addEffect": "ะ—ะฐัั‚ะพััƒะนั‚ะต ะตั„ะตะบั‚ `{0}` ั„ั–ะปัŒั‚ั€.", - "clearEffect": "ะ—ะฒัƒะบะพะฒั– ะตั„ะตะบั‚ะธ ะฑัƒะปะธ ะพั‡ะธั‰ะตะฝั–!", - "filterTagAlreadyInUse": "ะฆะตะน ะทะฒัƒะบะพะฒะธะน ะตั„ะตะบั‚ ัƒะถะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั! ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต /cleareffect <ะขะตะณ>, ั‰ะพะฑ ะฒะธะดะฐะปะธั‚ะธ ะนะพะณะพ.", - "playlistViewTitle": "๐Ÿ“œ ะฃัั– ะฟะปะตะนะปะธัั‚ะธ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ {0}", - "playlistViewHeaders": "ID:,ะงะฐั:,ะะฐะทะฒะฐ:,ะขั€ะตะบะธ:", - "playlistFooter": "ะ’ะฒะตะดะธั‚ะต /playlist play [ะฟะปะตะนะปะธัั‚], ั‡ั‚ะพะฑั‹ ะดะพะฑะฐะฒะธั‚ัŒ ะฟะปะตะนะปะธัั‚ ะฒ ะพั‡ะตั€ะตะดัŒ.", - "playlistNotFound": "ะŸะปะตะนะปะธัั‚ [`{0}`] ะฝะต ะทะฝะฐะนะดะตะฝะพ. ะ’ะฒะตะดั–ั‚ัŒ /playlist view, ั‰ะพะฑ ะฟะพะดะธะฒะธั‚ะธัั ะฒัั– ะฒะฐัˆั– ะฟะปะตะนะปะธัั‚ะธ.", - "playlistNotAccess": "ะ’ะธะฑะฐั‡ั‚ะต! ะฃ ะฒะฐั ะฝะตะผะฐั” ะดะพัั‚ัƒะฟัƒ ะดะพ ั†ัŒะพะณะพ ะฟะปะตะนะปะธัั‚ะฐ!", - "playlistNoTrack": "ะ’ะธะฑะฐั‡ั‚ะต! ะฃ ะฟะปะตะนะปะธัั‚ั– [`{0}`] ะฝะตะผะฐั” ั‚ั€ะตะบั–ะฒ.", - "playlistNotAllow": "ะฆั ะบะพะผะฐะฝะดะฐ ะฝะต ะดะพะทะฒะพะปะตะฝะฐ ะดะปั ะฟะพะฒ'ัะทะฐะฝะธั… ั– ะทะฐะณะฐะปัŒะฝะธั… ะฟะปะตะนะปะธัั‚ั–ะฒ.", - "playlistPlay": "ะ”ะพะดะฐะฝะพ ะฟะปะตะนะปะธัั‚ [`{0}`] ะท `{1}` ะฟั–ัะฝัะผะธ ะฒ ั‡ะตั€ะณัƒ.", - "playlistOverText": "ะ’ะธะฑะฐั‡ั‚ะต! ะ†ะผ'ั ะฟะปะตะนะปะธัั‚ะฐ ะฝะต ะผะพะถะต ะฟะตั€ะตะฒะธั‰ัƒะฒะฐั‚ะธ 10 ัะธะผะฒะพะปั–ะฒ.", - "playlistSameName": "ะ’ะธะฑะฐั‡ั‚ะต! ะฆะต ั–ะผ'ั ะฝะต ะผะพะถะต ะทะฑั–ะณะฐั‚ะธัั ะท ะฒะฐัˆะธะผ ะฝะพะฒะธะผ ั–ะผ'ัะผ.", - "playlistDeleteError": "ะ’ะธ ะฝะต ะผะพะถะตั‚ะต ะฒะธะดะฐะปะธั‚ะธ ะฟะปะตะนะปะธัั‚ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ.", - "playlistRemove": "ะ’ะธ ะฒะธะดะฐะปะธะปะธ ะฟะปะตะนะปะธัั‚ [`{0}`].", - "playlistSendErrorPlayer": "ะ’ะธะฑะฐั‡ั‚ะต! ะ’ะธ ะฝะต ะผะพะถะตั‚ะต ะฝะฐะดั–ัะปะฐั‚ะธ ะทะฐะฟั€ะพัˆะตะฝะฝั ัะฐะผะพะผัƒ ัะพะฑั–.", - "playlistSendErrorBot": "ะ’ะธะฑะฐั‡ั‚ะต! ะ’ะธ ะฝะต ะผะพะถะตั‚ะต ะฝะฐะดั–ัะปะฐั‚ะธ ะทะฐะฟั€ะพัˆะตะฝะฝั ะฑะพั‚ัƒ.", - "playlistBelongs": "ะ’ะธะฑะฐั‡ั‚ะต! ะฆะตะน ะฟะปะตะนะปะธัั‚ ะฝะฐะปะตะถะธั‚ัŒ <@{0}>.", - "playlistShare": "ะ’ะธะฑะฐั‡ั‚ะต! ะฆะธะผ ะฟะปะตะนะปะธัั‚ะพะผ ัƒะถะต ะฟะพะดั–ะปะธะปะธัั ะท {0}.", - "playlistSent": "ะ’ะธะฑะฐั‡ั‚ะต! ะ’ะธ ะฒะถะต ะฝะฐะดั–ัะปะฐะปะธ ะทะฐะฟั€ะพัˆะตะฝะฝั ั€ะฐะฝั–ัˆะต.", - "noPlaylistAcc": "{0} ะฝะต ัั‚ะฒะพั€ะธะฒ ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะฟะปะตะนะปะธัั‚ะฐ.", - "overPlaylistCreation": "ะ’ะธ ะฝะต ะผะพะถะตั‚ะต ัั‚ะฒะพั€ัŽะฒะฐั‚ะธ ะฑั–ะปัŒัˆะต `{0}` ะฟะปะตะนะปะธัั‚ั–ะฒ!", - "playlistExists": "ะŸะปะตะนะปะธัั‚ [`{0}`] ะฒะถะต ั–ัะฝัƒั”.", - "playlistNotInvalidUrl": "ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฒะฒะตะดั–ั‚ัŒ ะดั–ะนัะฝะต ะฟะพัะธะปะฐะฝะฝั ะฐะฑะพ ะฟัƒะฑะปั–ั‡ะฝะต ะฟะปะตะนะปะธัั‚-ะฟะพัะธะปะฐะฝะฝั ะฝะฐ Spotify ะฐะฑะพ YouTube.", - "playlistCreated": "ะ’ะธ ัั‚ะฒะพั€ะธะปะธ ะฟะปะตะนะปะธัั‚ `{0}`. ะ’ะฒะตะดั–ั‚ัŒ /playlist view ะดะปั ะพั‚ั€ะธะผะฐะฝะฝั ะดะพะดะฐั‚ะบะพะฒะพั— ั–ะฝั„ะพั€ะผะฐั†ั–ั—.", - "playlistRenamed": "ะ’ะธ ะฟะตั€ะตะนะผะตะฝัƒะฒะฐะปะธ `{0}` ะฝะฐ `{1}`.", - "playlistLimitTrack": "ะ’ะธ ะดะพััะณะปะธ ะปั–ะผั–ั‚ัƒ! ะ’ะธ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ ั‚ั–ะปัŒะบะธ `{0}` ะฟั–ัะตะฝัŒ ะดะพ ัะฒะพะณะพ ะฟะปะตะนะปะธัั‚ะฐ.", - "playlistPlaylistLink": "ะ’ะฐะผ ะฝะต ะดะพะทะฒะพะปะตะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฟะพัะธะปะฐะฝะฝั ะฝะฐ ะฟะปะตะนะปะธัั‚.", - "playlistStream": "ะ’ะฐะผ ะฝะต ะดะพะทะฒะพะปะตะฝะพ ะดะพะดะฐะฒะฐั‚ะธ ะฟะพั‚ะพะบะพะฒั– ะฒั–ะดะตะพ ัƒ ัะฒั–ะน ะฟะปะตะนะปะธัั‚.", - "playlistPositionNotFound": "ะะต ะฒะดะฐั”ั‚ัŒัั ะทะฝะฐะนั‚ะธ ะฟะพะทะธั†ั–ัŽ `{0}` ัƒ ะฒะฐัˆะพะผัƒ ะฟะปะตะนะปะธัั‚ั– [`{1}`]!", - "playlistRemoved": "๐Ÿ‘‹ ะ’ะธะดะฐะปะตะฝะพ **{0}** ะท ะฟะปะตะนะปะธัั‚ะฐ {1} [`{2}`].", - "playlistClear": "ะ’ะธ ัƒัะฟั–ัˆะฝะพ ะพั‡ะธัั‚ะธะปะธ ัะฒั–ะน ะฟะปะตะนะปะธัั‚ [`{0}`].", - "playlistView": "ะŸะตั€ะตะณะปัะด ะฟะปะตะนะปะธัั‚ั–ะฒ", - "playlistViewDesc": "```ะ†ะผ'ั | ID: {0} | {1}\nะฃััŒะพะณะพ ั‚ั€ะตะบั–ะฒ: {2}\nะ’ะปะฐัะฝะธะบ: {3}\nะขะธะฟ: {4}\n```", - "playlistViewPermsValue": "๐Ÿ“– ะงะธั‚ะฐะฝะฝั: โœ“ โœ๐Ÿฝ ะ—ะฐะฟะธั: {0} ๐Ÿ—‘๏ธ ะ’ะธะดะฐะปะตะฝะฝั: {1}", - "playlistViewPermsValue2": "๐Ÿ“– ะงะธั‚ะฐะฝะฝั: {0}", - "playlistViewTrack": "ะขั€ะตะบะธ", - "playlistViewPage": "ะกั‚ะพั€ั–ะฝะบะฐ: {0}/{1} | ะ—ะฐะณะฐะปัŒะฝะฐ ั‚ั€ะธะฒะฐะปั–ัั‚ัŒ: {2}", - "inboxFull": "ะ’ะธะฑะฐั‡ั‚ะต! ะŸะพัˆั‚ะพะฒะฐ ัะบั€ะธะฝัŒะบะฐ {0} ะฟะตั€ะตะฟะพะฒะฝะตะฝะฐ.", - "inboxNoMsg": "ะฃ ะฒะฐัˆั–ะน ะฟะพัˆั‚ะพะฒั–ะน ัะบั€ะธะฝัŒั†ั– ะฝะตะผะฐั” ะฟะพะฒั–ะดะพะผะปะตะฝัŒ.", - "invitationSent": "ะ—ะฐะฟั€ะพัˆะตะฝะฝั ะฝะฐะดั–ัะปะฐะฝะพ {0}.", - "notInChannel": "{0}, ะดะปั ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะณะพะปะพัะพะฒะธั… ะบะพะผะฐะฝะด ะฒะธ ะผะฐั”ั‚ะต ะฟะตั€ะตะฑัƒะฒะฐั‚ะธ ะฒ {1}. ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฟะตั€ะตะทะฐะนะดั–ั‚ัŒ, ัะบั‰ะพ ะฒะธ ะฒะถะต ะฒ ะณะพะปะพัั–!", - "noTrackPlaying": "ะ—ะฐั€ะฐะท ะฝะตะผะฐั” ะฟั–ัะตะฝัŒ, ัะบั– ะณั€ะฐัŽั‚ัŒ.", - "noTrackFound": "ะŸั–ัะฝั– ะท ั‚ะฐะบะธะผ ะทะฐะฟะธั‚ะพะผ ะฝะต ะทะฝะฐะนะดะตะฝะพ! ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฒะบะฐะถั–ั‚ัŒ ะดั–ะนัะฝะต ะฟะพัะธะปะฐะฝะฝั.", - "noLinkSupport": "ะšะพะผะฐะฝะดะฐ ะฟะพัˆัƒะบัƒ ะฝะต ะฟั–ะดั‚ั€ะธะผัƒั” ะฟะพัะธะปะฐะฝะฝั!", - "voted": "ะ’ะธ ะฟั€ะพะณะพะปะพััƒะฒะฐะปะธ!", - "missingPosPerm": "ะขั–ะปัŒะบะธ DJ ะฐะฑะพ ะฐะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€ะธ ะผะพะถัƒั‚ัŒ ะทะผั–ะฝัŽะฒะฐั‚ะธ ะฟะพะทะธั†ั–ัŽ.", - "missingModePerm": "ะขั–ะปัŒะบะธ DJ ะฐะฑะพ ะฐะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€ะธ ะผะพะถัƒั‚ัŒ ะฟะตั€ะตะผะบะฝัƒั‚ะธ ั€ะตะถะธะผ ั†ะธะบะปัƒ.", - "missingQueuePerm": "ะขั–ะปัŒะบะธ DJ ะฐะฑะพ ะฐะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€ะธ ะผะพะถัƒั‚ัŒ ะฒะธะดะฐะปัั‚ะธ ั‚ั€ะตะบะธ ะท ั‡ะตั€ะณะธ.", - "missingAutoPlayPerm": "ะขั–ะปัŒะบะธ DJ ะฐะฑะพ ะฐะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€ะธ ะผะพะถัƒั‚ัŒ ะฒะผะธะบะฐั‚ะธ ะฐะฑะพ ะฒะธะผะธะบะฐั‚ะธ ั€ะตะถะธะผ autoplay!", - "missingFunctionPerm": "ะขั–ะปัŒะบะธ DJ ะฐะฑะพ ะฐะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€ะธ ะผะพะถัƒั‚ัŒ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั†ัŽ ั„ัƒะฝะบั†ั–ัŽ.", - "timeFormatError": "ะะตะฟั€ะฐะฒะธะปัŒะฝะธะน ั„ะพั€ะผะฐั‚ ั‡ะฐััƒ. ะŸั€ะธะบะปะฐะด: 2:42", - "lyricsNotFound": "ะขะตะบัั‚ ะฟั–ัะฝั– ะฝะต ะทะฝะฐะนะดะตะฝะพ. ะ’ะฒะตะดั–ั‚ัŒ /lyrics <ะะฐะทะฒะฐ ะฟั–ัะฝั–> <ะะฒั‚ะพั€> ะดะปั ะฟะพัˆัƒะบัƒ ั‚ะตะบัั‚ัƒ.", - "missingTrackInfo": "ะ”ะตัะบะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ั‚ั€ะตะบ ะฒั–ะดััƒั‚ะฝั.", - "noVoiceChannel": "ะ“ะพะปะพัะพะฒะธะน ะบะฐะฝะฐะป ะฝะต ะทะฝะฐะนะดะตะฝะพ!", - "playlistAddError": "ะ’ะฐะผ ะฝะต ะดะพะทะฒะพะปะตะฝะพ ะดะพะดะฐะฒะฐั‚ะธ ะฟะพั‚ะพะบะพะฒั– ะฒั–ะดะตะพ ะฒ ะฟะปะตะนะปะธัั‚!", - "playlistAddError2": "ะกั‚ะฐะปะฐัั ะฟะพะผะธะปะบะฐ ะฟั–ะด ั‡ะฐั ะดะพะดะฐะฒะฐะฝะฝั ั‚ั€ะตะบั–ะฒ ัƒ ะฟะปะตะนะปะธัั‚!", - "playlistLimited": "ะ’ะธ ะดะพััะณะปะธ ะปั–ะผั–ั‚ัƒ! ะ’ะธ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ ั‚ั–ะปัŒะบะธ {0} ะฟั–ัะฝั– ะดะพ ัะฒะพะณะพ ะฟะปะตะนะปะธัั‚ะฐ.", - "playlistRepeated": "ะขะฐะบะธะน ัะฐะผะธะน ั‚ั€ะตะบ ัƒะถะต ั” ัƒ ะฒะฐัˆะพะผัƒ ะฟะปะตะนะปะธัั‚ั–!", - "playlistAdded": "โค๏ธ ะ”ะพะดะฐะฝะพ **{0}** ะดะพ ะฟะปะตะนะปะธัั‚ะฐ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ {1} [`{2}`]!", - "playerDropdown": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะฟั–ัะฝัŽ ะดะปั ะฟะตั€ะตั…ะพะดัƒ ...", - "playerFilter": "ะ’ะธะฑะตั€ั–ั‚ัŒ ั„ั–ะปัŒั‚ั€ ะดะปั ะทะฐัั‚ะพััƒะฒะฐะฝะฝั ...", - "buttonBack": "ะะฐะทะฐะด", - "buttonPause": "ะŸั€ะธะทัƒะฟะธะฝะธั‚ะธ", - "buttonResume": "ะŸั€ะพะดะพะฒะถะธั‚ะธ", - "buttonSkip": "ะกะบั–ะฟะฝัƒั‚ะธ ั‚ั€ะตะบ", - "buttonLeave": "ะกะขะžะŸ", - "buttonLoop": "ะ—ะฐั†ะธะบะปะธั‚ะธ", - "buttonVolumeUp": "ะ“ัƒั‡ะฝั–ัั‚ัŒ +", - "buttonVolumeDown": "ะ“ัƒั‡ะฝั–ัั‚ัŒ -", - "buttonVolumeMute": "ะ’ะธะผะบะฝัƒั‚ะธ ะทะฒัƒะบ", - "buttonVolumeUnmute": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะทะฒัƒะบ", - "buttonAutoPlay": "Autoplay", - "buttonShuffle": "ะŸะตั€ะตะผั–ัˆะฐั‚ะธ", - "buttonForward": "ะ’ะฟะตั€ะตะด", - "buttonRewind": "ะะฐะทะฐะด", - "buttonLyrics": "ะขะตะบัั‚ะธ ะฟั–ัะตะฝัŒ", - "nowplayingDesc": "**ะ—ะฐั€ะฐะท ะณั€ะฐั”:**\n```{0}```", - "nowplayingField": "ะะฐัั‚ัƒะฟะฝะต:", - "nowplayingLink": "ะกะปัƒั…ะฐั‚ะธ ะฝะฐ {0}", - "connect": "ะŸั–ะดะบะปัŽั‡ะตะฝะพ ะดะพ {0}", - "live": "ะŸั€ัะผะธะน ะตั„ั–ั€", - "playlistLoad": "๐ŸŽถ ะ”ะพะดะฐะฝะพ ะฟะปะตะนะปะธัั‚ **{0}** ะท `{1}` ะฟั–ัะฝัะผะธ ะฒ ั‡ะตั€ะณัƒ.", - "trackLoad": "ะ”ะพะดะฐะฝะพ **[{0}](<{1}>)** ะฒั–ะด **{2}** (`{3}`) ะดะปั ะฟะพั‡ะฐั‚ะบัƒ ะฟั€ะพะณั€ะฐะฒะฐะฝะฝั.\n", - "trackLoad_pos": "ะ”ะพะดะฐะฝะพ **[{0}](<{1}>)** ะฒั–ะด **{2}** (`{3}`) ัƒ ั‡ะตั€ะณัƒ ะฝะฐ ะฟะพะทะธั†ั–ัŽ **{4}**\n", - "searchTitle": "ะŸะพัˆัƒะบ: {0}", - "searchDesc": "โžฅ ะŸะปะฐั‚ั„ะพั€ะผะฐ: {0} **{1}**\nโžฅ ะ ะตะทัƒะปัŒั‚ะฐั‚ะธ: **{2}**\n\n{3}", - "searchWait": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะฟั–ัะฝัŽ, ัะบัƒ ั…ะพั‡ะตั‚ะต ะดะพะดะฐั‚ะธ ะฒ ั‡ะตั€ะณัƒ.", - "searchTimeout": "ะŸะพัˆัƒะบ ะฟะตั€ะตั€ะฒะฐะฝะพ: ะฟะตั€ะตะฒะธั‰ะตะฝะพ ั‡ะฐั ะพั‡ั–ะบัƒะฒะฐะฝะฝั. ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ัะฟั€ะพะฑัƒะนั‚ะต ะฟั–ะทะฝั–ัˆะต.", - "searchSuccess": "ะŸั–ัะฝัŽ ะดะพะดะฐะฝะพ ะฒ ั‡ะตั€ะณัƒ.", - "queueTitle": "ะะฐัั‚ัƒะฟะฝะธะน ัƒ ั‡ะตั€ะทั–:", - "historyTitle": "ะ†ัั‚ะพั€ั–ั ั‡ะตั€ะณะธ:", - "viewTitle": "ะŸะพั‚ะพั‡ะฝะฐ ั‡ะตั€ะณะฐ", - "viewDesc": "**ะ—ะฐั€ะฐะทะพะผ ะณั€ะฐั”: [*ะฟะพัะธะปะฐะฝะฝั*]({0}) โฎฏ**\n{1}", - "viewFooter": "ะกั‚ะพั€ั–ะฝะบะฐ: {0}/{1} | ะ—ะฐะณะฐะปัŒะฝะฐ ั‚ั€ะธะฒะฐะปั–ัั‚ัŒ: {2}", - "pauseError": "ะŸะปะตั”ั€ ัƒะถะต ะฝะฐ ะฟะฐัƒะทั–.", - "pauseVote": "{0} ะฟั€ะพะณะพะปะพััƒะฒะฐะฒ ะทะฐ ะฟะฐัƒะทัƒ ะฟั–ัะฝั–. [{1}/{2}]", - "paused": "`{0}` ะŸะพัั‚ะฐะฒะธะฒ ะฟะปะตั”ั€ ะฝะฐ ะฟะฐัƒะทัƒ.", - "resumeError": "ะŸะปะตั”ั€ ะฝะต ะฝะฐ ะฟะฐัƒะทั–.", - "resumeVote": "{0} ะฟั€ะพะณะพะปะพััƒะฒะฐะฒ ะทะฐ ะฟั€ะพะดะพะฒะถะตะฝะฝั ะฟั–ัะฝั–. [{1}/{2}]", - "resumed": "`{0}` ะŸั€ะธะฑั€ะฐะฒ ะฟะปะตั”ั€ ั–ะท ะฟะฐัƒะทะธ.", - "shuffleError": "ะ”ะพะดะฐั‚ะธ ะฑั–ะปัŒัˆะต ะฟั–ัะตะฝัŒ ัƒ ั‡ะตั€ะณัƒ ะฟะตั€ะตะด ะฟะตั€ะตะผั–ัˆัƒะฒะฐะฝะฝัะผ.", - "shuffleVote": "{0} ะฟั€ะพะณะพะปะพััƒะฒะฐะฒ ะทะฐ ะฟะตั€ะตะผั–ัˆัƒะฒะฐะฝะฝั ั‡ะตั€ะณะธ. [{1}/{2}]", - "shuffled": "ะงะตั€ะณะฐ ะฟะตั€ะตะผั–ัˆะฐะฝะฐ.", - "skipError": "ะะตะผะฐั” ะฟั–ัะตะฝัŒ ะดะปั ะฟั€ะพะฟัƒัะบัƒ.", - "skipVote": "{0} ะฟั€ะพะณะพะปะพััƒะฒะฐะฒ ะทะฐ ะฟั€ะพะฟัƒัะบ ะฟั–ัะฝั–. [{1}/{2}]", - "skipped": "`{0}` ะฟั€ะพะฟัƒัั‚ะธะฒ ะฟั–ัะฝัŽ.", - "backVote": "{0} ะฟั€ะพะณะพะปะพััƒะฒะฐะฒ ะทะฐ ะฟะพะฒะตั€ะฝะตะฝะฝั ะดะพ ะฟะพะฟะตั€ะตะดะฝัŒะพั— ะฟั–ัะฝั–. [{1}/{2}]", - "backed": "`{0}` ะฟะพะฒะตั€ะฝัƒะฒัั ะดะพ ะฟะพะฟะตั€ะตะดะฝัŒะพั— ะฟั–ัะฝั–.", - "leaveVote": "{0} ะฟั€ะพะณะพะปะพััƒะฒะฐะฒ ะทะฐ ะทัƒะฟะธะฝะบัƒ ะฟะปะตั”ั€ะฐ. [{1}/{2}]", - "left": "`{0}` ะทัƒะฟะธะฝะธะฒ ะฟะปะตั”ั€.", - "seek": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ะฟะปะตั”ั€ ะฝะฐ **{0}**", - "repeat": "ะ ะตะถะธะผ ั†ะธะบะปัƒ ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ ะฝะฐ `{0}`", - "cleared": "ะžั‡ะธั‰ะตะฝะพ ะฒัั– ั‚ั€ะตะบะธ ะฒ `{0}`", - "removed": "ะ’ะธะดะฐะปะตะฝะพ `{0}` ั‚ั€ะตะบั–ะฒ ั–ะท ั‡ะตั€ะณะธ.", - "forward": "ะŸะตั€ะตะผะพั‚ะฐั‚ะธ ะฟะปะตั”ั€ ะฒะฟะตั€ะตะด ะฝะฐ **{0}**", - "rewind": "ะŸะตั€ะตะผะพั‚ะฐั‚ะธ ะฟะปะตั”ั€ ะฝะฐะทะฐะด ะฝะฐ **{0}**", - "replay": "ะŸะพะฒั‚ะพั€ะตะฝะฝั ะฟะพั‚ะพั‡ะฝะพั— ะฟั–ัะฝั–.", - "swapped": "ะขั€ะตะบะธ `{0}` ั– `{1}` ะพะฑะผั–ะฝัะฝั– ะผั–ัั†ัะผะธ", - "moved": "ะขั€ะตะบ `{0}` ะทะผั–ั‰ะตะฝะธะน ะฝะฐ `{1}`", - "autoplay": "ะ ะตะถะธะผ autoplay ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ ะฝะฐ **{0}**", - "notdj": "ะ’ะธ ะฝะต DJ. ะŸะพั‚ะพั‡ะฝะธะน DJ: {0}.", - "djToMe": "ะ’ะธ ะฝะต ะผะพะถะตั‚ะต ะฟะตั€ะตะดะฐั‚ะธ ั€ะพะปัŒ DJ ัะพะฑั– ะฐะฑะพ ะฑะพั‚ัƒ.", - "djNotInChannel": "`{0}` ะฝะต ะทะฝะฐั…ะพะดะธั‚ัŒัั ะฒ ะณะพะปะพัะพะฒะพะผัƒ ะบะฐะฝะฐะปั–.", - "djswap": "ะ’ะธ ะฟะตั€ะตะดะฐะปะธ ั€ะพะปัŒ DJ `{0}`.", - "voicelinkQueueFull": "ะ’ะธะฑะฐั‡ั‚ะต, ะฒะธ ะดะพััะณะปะธ ะผะฐะบัะธะผะฐะปัŒะฝะพั— ะบั–ะปัŒะบะพัั‚ั– `{0}` ั‚ั€ะตะบั–ะฒ ัƒ ั‡ะตั€ะทั–!", - "voicelinkOutofList": "ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฝะฐะดะฐะนั‚ะต ะดั–ะนัะฝะธะน ั–ะฝะดะตะบั ั‚ั€ะตะบัƒ!", - "voicelinkDuplicateTrack": "ะ’ะธะฑะฐั‡ั‚ะต, ั†ะตะน ั‚ั€ะตะบ ัƒะถะต ั” ะฒ ั‡ะตั€ะทั–.", - "decodeError": "ะฉะพััŒ ะฟั–ัˆะปะพ ะฝะต ั‚ะฐะบ ะฟั–ะด ั‡ะฐั ะดะตะบะพะดัƒะฒะฐะฝะฝั ั„ะฐะนะปัƒ!", - "invalidStartTime": "ะะตะดั–ะนัะฝะตะฝะธะน ั‡ะฐั ะฟะพั‡ะฐั‚ะบัƒ! ะงะฐั ะผะฐั” ะฑัƒั‚ะธ ะฒ ะผะตะถะฐั… `00:00` ั‚ะฐ `{0}`.", - "invalidEndTime": "ะะตะดั–ะนัะฝะตะฝะธะน ั‡ะฐั ะทะฐะบั–ะฝั‡ะตะฝะฝั! ะงะฐั ะผะฐั” ะฑัƒั‚ะธ ะฒ ะผะตะถะฐั… `00:00` ั‚ะฐ `{0}`.", - "invalidTimeOrder": "ะงะฐั ะทะฐะบั–ะฝั‡ะตะฝะฝั ะฝะต ะผะพะถะต ะฑัƒั‚ะธ ะผะตะฝัˆะธะผ ะฐะฑะพ ั€ั–ะฒะฝะธะผ ั‡ะฐััƒ ะฟะพั‡ะฐั‚ะบัƒ.", - "setStageAnnounceTemplate": "ะ“ะพั‚ะพะฒะพ! ะ’ั–ะดั‚ะตะฟะตั€ ัั‚ะฐั‚ัƒั ะณะพะปะพััƒ, ัะบ ั‚ะพะน, ะฒ ัะบะพะผัƒ ะฒะธ ะทะฐั€ะฐะท ะฟะตั€ะตะฑัƒะฒะฐั”ั‚ะต, ะฑัƒะดะต ะฝะฐะทะธะฒะฐั‚ะธัั ะฒั–ะดะฟะพะฒั–ะดะฝะพ ะดะพ ะฒะฐัˆะพะณะพ ัˆะฐะฑะปะพะฝัƒ. ะ’ะธ ะฟะพะฒะธะฝะฝั– ะฟะพะฑะฐั‡ะธั‚ะธ ะพะฝะพะฒะปะตะฝะฝั ั‡ะตั€ะตะท ะบั–ะปัŒะบะฐ ัะตะบัƒะฝะด.", - "createSongRequestChannel": "ะšะฐะฝะฐะป ะดะปั ะทะฐะฟะธั‚ั–ะฒ ะฟั–ัะตะฝัŒ ({0}) ัั‚ะฒะพั€ะตะฝะพ! ะ’ะธ ะผะพะถะตั‚ะต ะฟะพั‡ะฐั‚ะธ ะทะฐะฟะธั‚ัƒะฒะฐั‚ะธ ะฑัƒะดัŒ-ัะบัƒ ะฟั–ัะฝัŽ ะทะฐ ะฝะฐะทะฒะพัŽ ะฐะฑะพ URL ัƒ ั†ัŒะพะผัƒ ะบะฐะฝะฐะปั– ะฑะตะท ะฝะตะพะฑั…ั–ะดะฝะพัั‚ั– ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฟั€ะตั„ั–ะบั ะฑะพั‚ะฐ." -} \ No newline at end of file + "common": { + "errors": { + "unknown": "โš ๏ธ ะฉะพััŒ ะฟั–ัˆะปะพ ะฝะต ั‚ะฐะบ ะฟั–ะด ั‡ะฐั ะฒะธะบะพะฝะฐะฝะฝั ะบะพะผะฐะฝะดะธ! ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ัะฟั€ะพะฑัƒะนั‚ะต ะฟั–ะทะฝั–ัˆะต ะฐะฑะพ ะฟั€ะธั”ะดะฝะฐะนั‚ะตัั ะดะพ ะฝะฐัˆะพะณะพ ัะตั€ะฒะตั€ะฐ Discord ะดะปั ะพั‚ั€ะธะผะฐะฝะฝั ะดะพะดะฐั‚ะบะพะฒะพั— ะฟั–ะดั‚ั€ะธะผะบะธ.", + "missingIntents": "ะ’ะธะฑะฐั‡ั‚ะต, ะฐะปะต ั†ัŽ ะบะพะผะฐะฝะดัƒ ะฝะต ะผะพะถะฝะฐ ะฒะธะบะพะฝะฐั‚ะธ, ะพัะบั–ะปัŒะบะธ ัƒ ะฑะพั‚ะฐ ะฒั–ะดััƒั‚ะฝั–ะน ะฝะตะพะฑั…ั–ะดะฝะธะน ะทะฐะฟะธั‚ ะฝะฐ ั–ะฝั‚ะตะฝั†ั–ัŽ: `({0})`." + }, + "status": { + "enabled": "ะฒะบะป", + "disabled": "ะฒะธะบะป", + "live": "ะŸั€ัะผะธะน ะตั„ั–ั€" + } + }, + "player": { + "errors": { + "nodeReconnect": "ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ัะฟั€ะพะฑัƒะนั‚ะต ะทะฝะพะฒัƒ! ะŸั–ัะปั ะฟะตั€ะตะฟั–ะดะบะปัŽั‡ะตะฝะฝั ะฒัƒะทะปะฐ.", + "noPlaySource": "ะะตะผะพะถะปะธะฒะพ ะทะฝะฐะนั‚ะธ ั€ะพะฑะพั‡ั– ะดะถะตั€ะตะปะฐ!", + "noPlayer": "ะะฐ ั†ัŒะพะผัƒ ัะตั€ะฒะตั€ั– ะฝะต ะทะฝะฐะนะดะตะฝะพ ะถะพะดะฝะพะณะพ ะฐะบั‚ะธะฒะฝะพะณะพ ะฟะปะตั”ั€ะฐ.", + "noTrackPlaying": "ะ—ะฐั€ะฐะท ะฝะตะผะฐั” ะฟั–ัะตะฝัŒ, ัะบั– ะณั€ะฐัŽั‚ัŒ.", + "noTrackFound": "ะŸั–ัะฝั– ะท ั‚ะฐะบะธะผ ะทะฐะฟะธั‚ะพะผ ะฝะต ะทะฝะฐะนะดะตะฝะพ! ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฒะบะฐะถั–ั‚ัŒ ะดั–ะนัะฝะต ะฟะพัะธะปะฐะฝะฝั." + }, + "dropdown": { + "trackSelect": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะฟั–ัะฝัŽ ะดะปั ะฟะตั€ะตั…ะพะดัƒ ...", + "filterSelect": "ะ’ะธะฑะตั€ั–ั‚ัŒ ั„ั–ะปัŒั‚ั€ ะดะปั ะทะฐัั‚ะพััƒะฒะฐะฝะฝั ..." + }, + "buttons": { + "back": "ะะฐะทะฐะด", + "pause": "ะŸั€ะธะทัƒะฟะธะฝะธั‚ะธ", + "resume": "ะŸั€ะพะดะพะฒะถะธั‚ะธ", + "skip": "ะกะบั–ะฟะฝัƒั‚ะธ ั‚ั€ะตะบ", + "leave": "ะกะขะžะŸ", + "loop": "ะ—ะฐั†ะธะบะปะธั‚ะธ", + "volumeUp": "ะ“ัƒั‡ะฝั–ัั‚ัŒ +", + "volumeDown": "ะ“ัƒั‡ะฝั–ัั‚ัŒ -", + "volumeMute": "ะ’ะธะผะบะฝัƒั‚ะธ ะทะฒัƒะบ", + "volumeUnmute": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะทะฒัƒะบ", + "autoPlay": "Autoplay", + "shuffle": "ะŸะตั€ะตะผั–ัˆะฐั‚ะธ", + "forward": "ะ’ะฟะตั€ะตะด", + "rewind": "ะะฐะทะฐะด", + "lyrics": "ะขะตะบัั‚ะธ ะฟั–ัะตะฝัŒ" + }, + "playback": { + "nowplayingDesc": "**ะ—ะฐั€ะฐะท ะณั€ะฐั”:**\n```{0}```", + "nowplayingField": "ะะฐัั‚ัƒะฟะฝะต:", + "nowplayingLink": "ะกะปัƒั…ะฐั‚ะธ ะฝะฐ {0}", + "playlistLoad": "๐ŸŽถ ะ”ะพะดะฐะฝะพ ะฟะปะตะนะปะธัั‚ **{0}** ะท `{1}` ะฟั–ัะฝัะผะธ ะฒ ั‡ะตั€ะณัƒ.", + "trackLoad": "ะ”ะพะดะฐะฝะพ **[{0}](<{1}>)** ะฒั–ะด **{2}** (`{3}`) ะดะปั ะฟะพั‡ะฐั‚ะบัƒ ะฟั€ะพะณั€ะฐะฒะฐะฝะฝั.\n", + "trackLoadPos": "ะ”ะพะดะฐะฝะพ **[{0}](<{1}>)** ะฒั–ะด **{2}** (`{3}`) ัƒ ั‡ะตั€ะณัƒ ะฝะฐ ะฟะพะทะธั†ั–ัŽ **{4}**\n" + }, + "controls": { + "pause": { + "error": "ะŸะปะตั”ั€ ัƒะถะต ะฝะฐ ะฟะฐัƒะทั–.", + "vote": "{0} ะฟั€ะพะณะพะปะพััƒะฒะฐะฒ ะทะฐ ะฟะฐัƒะทัƒ ะฟั–ัะฝั–. [{1}/{2}]", + "success": "`{0}` ะŸะพัั‚ะฐะฒะธะฒ ะฟะปะตั”ั€ ะฝะฐ ะฟะฐัƒะทัƒ." + }, + "resume": { + "error": "ะŸะปะตั”ั€ ะฝะต ะฝะฐ ะฟะฐัƒะทั–.", + "vote": "{0} ะฟั€ะพะณะพะปะพััƒะฒะฐะฒ ะทะฐ ะฟั€ะพะดะพะฒะถะตะฝะฝั ะฟั–ัะฝั–. [{1}/{2}]", + "success": "`{0}` ะŸั€ะธะฑั€ะฐะฒ ะฟะปะตั”ั€ ั–ะท ะฟะฐัƒะทะธ." + }, + "shuffle": { + "error": "ะ”ะพะดะฐั‚ะธ ะฑั–ะปัŒัˆะต ะฟั–ัะตะฝัŒ ัƒ ั‡ะตั€ะณัƒ ะฟะตั€ะตะด ะฟะตั€ะตะผั–ัˆัƒะฒะฐะฝะฝัะผ.", + "vote": "{0} ะฟั€ะพะณะพะปะพััƒะฒะฐะฒ ะทะฐ ะฟะตั€ะตะผั–ัˆัƒะฒะฐะฝะฝั ั‡ะตั€ะณะธ. [{1}/{2}]", + "success": "ะงะตั€ะณะฐ ะฟะตั€ะตะผั–ัˆะฐะฝะฐ." + }, + "skip": { + "error": "ะะตะผะฐั” ะฟั–ัะตะฝัŒ ะดะปั ะฟั€ะพะฟัƒัะบัƒ.", + "vote": "{0} ะฟั€ะพะณะพะปะพััƒะฒะฐะฒ ะทะฐ ะฟั€ะพะฟัƒัะบ ะฟั–ัะฝั–. [{1}/{2}]", + "success": "`{0}` ะฟั€ะพะฟัƒัั‚ะธะฒ ะฟั–ัะฝัŽ." + }, + "back": { + "vote": "{0} ะฟั€ะพะณะพะปะพััƒะฒะฐะฒ ะทะฐ ะฟะพะฒะตั€ะฝะตะฝะฝั ะดะพ ะฟะพะฟะตั€ะตะดะฝัŒะพั— ะฟั–ัะฝั–. [{1}/{2}]", + "success": "`{0}` ะฟะพะฒะตั€ะฝัƒะฒัั ะดะพ ะฟะพะฟะตั€ะตะดะฝัŒะพั— ะฟั–ัะฝั–." + }, + "leave": { + "vote": "{0} ะฟั€ะพะณะพะปะพััƒะฒะฐะฒ ะทะฐ ะทัƒะฟะธะฝะบัƒ ะฟะปะตั”ั€ะฐ. [{1}/{2}]", + "success": "`{0}` ะทัƒะฟะธะฝะธะฒ ะฟะปะตั”ั€." + }, + "seek": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ะฟะปะตั”ั€ ะฝะฐ **{0}**", + "repeat": "ะ ะตะถะธะผ ั†ะธะบะปัƒ ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ ะฝะฐ `{0}`", + "forward": "ะŸะตั€ะตะผะพั‚ะฐั‚ะธ ะฟะปะตั”ั€ ะฒะฟะตั€ะตะด ะฝะฐ **{0}**", + "rewind": "ะŸะตั€ะตะผะพั‚ะฐั‚ะธ ะฟะปะตั”ั€ ะฝะฐะทะฐะด ะฝะฐ **{0}**", + "replay": "ะŸะพะฒั‚ะพั€ะตะฝะฝั ะฟะพั‚ะพั‡ะฝะพั— ะฟั–ัะฝั–.", + "autoplay": "ะ ะตะถะธะผ autoplay ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ ะฝะฐ **{0}**" + } + }, + "voice": { + "connection": { + "noChannel": "ะะตะผะฐั” ะณะพะปะพัะพะฒะพะณะพ ะบะฐะฝะฐะปัƒ ะดะปั ะฟั–ะดะบะปัŽั‡ะตะฝะฝั. ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฒะบะฐะถั–ั‚ัŒ ะฐะฑะพ ะฟั€ะธั”ะดะฝะฐะนั‚ะตัั ะดะพ ะพะดะฝะพะณะพ.", + "alreadyConnected": "ะฃะถะต ะฟั–ะดะบะปัŽั‡ะตะฝะธะน ะดะพ ะณะพะปะพัะพะฒะพะณะพ ะบะฐะฝะฐะปัƒ.", + "noPermission": "ะ’ะธะฑะฐั‡ั‚ะต! ะฃ ะผะตะฝะต ะฝะตะผะฐั” ะดะพะทะฒะพะปัƒ ะฝะฐ ะฟั–ะดะบะปัŽั‡ะตะฝะฝั ะฐะฑะพ ั€ะพะทะผะพะฒัƒ ัƒ ะฒะฐัˆะพะผัƒ ะณะพะปะพัะพะฒะพะผัƒ ะบะฐะฝะฐะปั–.", + "notInChannel": "{0}, ะดะปั ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั ะณะพะปะพัะพะฒะธั… ะบะพะผะฐะฝะด ะฒะธ ะผะฐั”ั‚ะต ะฟะตั€ะตะฑัƒะฒะฐั‚ะธ ะฒ {1}. ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฟะตั€ะตะทะฐะนะดั–ั‚ัŒ, ัะบั‰ะพ ะฒะธ ะฒะถะต ะฒ ะณะพะปะพัั–!", + "connect": "ะŸั–ะดะบะปัŽั‡ะตะฝะพ ะดะพ {0}" + }, + "stageChannel": { + "setAnnounceTemplate": "ะ“ะพั‚ะพะฒะพ! ะ’ั–ะดั‚ะตะฟะตั€ ัั‚ะฐั‚ัƒั ะณะพะปะพััƒ, ัะบ ั‚ะพะน, ะฒ ัะบะพะผัƒ ะฒะธ ะทะฐั€ะฐะท ะฟะตั€ะตะฑัƒะฒะฐั”ั‚ะต, ะฑัƒะดะต ะฝะฐะทะธะฒะฐั‚ะธัั ะฒั–ะดะฟะพะฒั–ะดะฝะพ ะดะพ ะฒะฐัˆะพะณะพ ัˆะฐะฑะปะพะฝัƒ. ะ’ะธ ะฟะพะฒะธะฝะฝั– ะฟะพะฑะฐั‡ะธั‚ะธ ะพะฝะพะฒะปะตะฝะฝั ั‡ะตั€ะตะท ะบั–ะปัŒะบะฐ ัะตะบัƒะฝะด." + } + }, + "permissions": { + "noCreatePermission": "ะ’ะธะฑะฐั‡ั‚ะต! ะฃ ะผะตะฝะต ะฝะตะผะฐั” ะฟั€ะฐะฒ ะดะปั ัั‚ะฒะพั€ะตะฝะฝั ะบะฐะฝะฐะปัƒ ะทะฐะฟะธั‚ั–ะฒ ะฝะฐ ะฟั–ัะฝั–.", + "missingPosition": "ะขั–ะปัŒะบะธ DJ ะฐะฑะพ ะฐะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€ะธ ะผะพะถัƒั‚ัŒ ะทะผั–ะฝัŽะฒะฐั‚ะธ ะฟะพะทะธั†ั–ัŽ.", + "missingMode": "ะขั–ะปัŒะบะธ DJ ะฐะฑะพ ะฐะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€ะธ ะผะพะถัƒั‚ัŒ ะฟะตั€ะตะผะบะฝัƒั‚ะธ ั€ะตะถะธะผ ั†ะธะบะปัƒ.", + "missingQueue": "ะขั–ะปัŒะบะธ DJ ะฐะฑะพ ะฐะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€ะธ ะผะพะถัƒั‚ัŒ ะฒะธะดะฐะปัั‚ะธ ั‚ั€ะตะบะธ ะท ั‡ะตั€ะณะธ.", + "missingAutoPlay": "ะขั–ะปัŒะบะธ DJ ะฐะฑะพ ะฐะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€ะธ ะผะพะถัƒั‚ัŒ ะฒะผะธะบะฐั‚ะธ ะฐะฑะพ ะฒะธะผะธะบะฐั‚ะธ ั€ะตะถะธะผ autoplay!", + "missingFunction": "ะขั–ะปัŒะบะธ DJ ะฐะฑะพ ะฐะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€ะธ ะผะพะถัƒั‚ัŒ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ั†ัŽ ั„ัƒะฝะบั†ั–ัŽ.", + "notDj": "ะ’ะธ ะฝะต DJ. ะŸะพั‚ะพั‡ะฝะธะน DJ: {0}.", + "djToSelf": "ะ’ะธ ะฝะต ะผะพะถะตั‚ะต ะฟะตั€ะตะดะฐั‚ะธ ั€ะพะปัŒ DJ ัะพะฑั– ะฐะฑะพ ะฑะพั‚ัƒ.", + "djNotInChannel": "`{0}` ะฝะต ะทะฝะฐั…ะพะดะธั‚ัŒัั ะฒ ะณะพะปะพัะพะฒะพะผัƒ ะบะฐะฝะฐะปั–.", + "djSwapped": "ะ’ะธ ะฟะตั€ะตะดะฐะปะธ ั€ะพะปัŒ DJ `{0}`." + }, + "settings": { + "actions": { + "languageNotFound": "ะœะพะฒะฝะธะน ะฟะฐะบะตั‚ ะฝะต ะทะฝะฐะนะดะตะฝะพ! ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฒะธะฑะตั€ั–ั‚ัŒ ะฝะฐัะฒะฝะธะน ะผะพะฒะฝะธะน ะฟะฐะบะตั‚.", + "languageChanged": "ะฃัะฟั–ัˆะฝะพ ะทะผั–ะฝะตะฝะพ ะฝะฐ ะผะพะฒะฝะธะน ะฟะฐะบะตั‚ `{0}`.", + "prefixSet": "ะ“ะพั‚ะพะฒะพ! ะœั–ะน ะฟั€ะตั„ั–ะบั ะฝะฐ ะฒะฐัˆะพะผัƒ ัะตั€ะฒะตั€ั– ั‚ะตะฟะตั€ `{0}`. ะกะฟั€ะพะฑัƒะนั‚ะต ะทะฐะฟัƒัั‚ะธั‚ะธ `{1}ping`, ั‰ะพะฑ ะฟะตั€ะตะฒั–ั€ะธั‚ะธ ะนะพะณะพ.", + "djSet": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ั€ะพะปัŒ DJ {0}.", + "queueModeSet": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ั€ะตะถะธะผ ั‡ะตั€ะณะธ ะฝะฐ `{0}`.", + "mode247": "ะขะตะฟะตั€ ัƒ ะฒะฐั `{0}` ั€ะตะถะธะผ 24/7.", + "bypassVote": "ะขะตะฟะตั€ ัƒ ะฒะฐั `{0}` ัะธัั‚ะตะผะฐ ะณะพะปะพััƒะฒะฐะฝะฝั.", + "volumeSet": "ะ’ัั‚ะฐะฝะพะฒะธั‚ะธ ะณัƒั‡ะฝั–ัั‚ัŒ ะฝะฐ `{0}`%", + "controllerToggled": "ะขะตะฟะตั€ ัƒ ะฒะฐั `{0}` ะบะพะฝั‚ั€ะพะปะตั€ ะผัƒะทะธะบะธ.", + "duplicateTrackToggled": "ะขะตะฟะตั€ ัƒ ะฒะฐั `{0}` ะทะฐะฟะพะฑั–ะณะฐะฝะฝั ะดัƒะฑะปัŽะฒะฐะฝะฝัŽ ั‚ั€ะตะบัƒ ะฒ ั‡ะตั€ะทั–.", + "controllerMsgToggled": "ะขะตะฟะตั€ ัƒ ะฒะฐั `{0}` ะฟะพะฒั–ะดะพะผะปะตะฝะฝั ะฒั–ะด ะบะพะฝั‚ั€ะพะปะตั€ะฐ ะผัƒะทะธะบะธ.", + "silentMsgToggled": "ะฃ ะฒะฐั {0} ั‚ะธั…ะธั… ะฟะพะฒั–ะดะพะผะปะตะฝัŒ." + }, + "menu": "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ัะตั€ะฒะตั€ะฐ | {0}", + "basic": { + "title": "โค๏ธ ะžัะฝะพะฒะฝะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ั:", + "value": "```ะŸั€ะตั„ั–ะบั: {0}\nะœะพะฒะฐ: {1}\nะฃะฒั–ะผะบะฝัƒั‚ะธ ะบะพะฝั‚ั€ะพะปะตั€ ะผัƒะทะธะบะธ: {2}\nDJ ั€ะพะปัŒ: {3}\nะžะฑั…ั–ะด ะณะพะปะพััƒะฒะฐะฝะฝั: {4}\n24/7: {5}\nะ“ั€ะพะผะบั–ัั‚ัŒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ: {6}%\nะงะฐั ะฟั€ะพะณั€ะฐะฒะฐะฝะฝั: {7}```" + }, + "queue": { + "title": "๐Ÿ”— ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ั‡ะตั€ะณัƒ:", + "value": "```ะ ะตะถะธะผ ั‡ะตั€ะณะธ: {0}\nะœะฐะบัะธะผะฐะปัŒะฝะฐ ะบั–ะปัŒะบั–ัั‚ัŒ ะฟั–ัะตะฝัŒ: {1}\nะ”ะพะทะฒะพะปะธั‚ะธ ะดัƒะฑะปัŽะฒะฐะฝะฝั ั‚ั€ะตะบั–ะฒ: {2}```" + }, + "voice": { + "title": "๐ŸŽค ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ัั‚ะฐะฝ ะณะพะปะพััƒ:" + }, + "permissions": { + "title": "โœจ ะŸั€ะฐะฒะฐ:", + "value": "```{0} ะะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€\n{1} ะšะตั€ัƒะฒะฐะฝะฝั_ะกะตั€ะฒะตั€ะพะผ\n{2} ะšะตั€ัƒะฒะฐะฝะฝั_ะšะฐะฝะฐะปะพะผ\n{3} ะšะตั€ัƒะฒะฐะฝะฝั_ะกะพะพะฑั‰ะตะฝะธัะผะธ```" + } + }, + "ping": { + "title1": "ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ะฑะพั‚ะฐ:", + "title2": "ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ะฟะปะตั”ั€:", + "field1": "````ID ะจะฐั€ะดะฐ: {0}/{1}\nะ—ะฐั‚ั€ะธะผะบะฐ ะจะฐั€ะดะฐ: {2:.3f}s {3}\nะ ะตะณั–ะพะฝ: {4}````", + "field2": "```ะ’ัƒะทะพะป: {0} - {1:.3f}s\nะ“ั€ะฐะฒั†ั–: {2}\nะ ะตะณั–ะพะฝ ะ“ะพะปะพััƒ: {3}```" + }, + "effects": { + "added": "ะ—ะฐัั‚ะพััƒะนั‚ะต ะตั„ะตะบั‚ `{0}` ั„ั–ะปัŒั‚ั€.", + "cleared": "ะ—ะฒัƒะบะพะฒั– ะตั„ะตะบั‚ะธ ะฑัƒะปะธ ะพั‡ะธั‰ะตะฝั–!", + "tagInUse": "ะฆะตะน ะทะฒัƒะบะพะฒะธะน ะตั„ะตะบั‚ ัƒะถะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั! ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต /cleareffect <ะขะตะณ>, ั‰ะพะฑ ะฒะธะดะฐะปะธั‚ะธ ะนะพะณะพ." + }, + "playlist": { + "view": { + "title": "ะฃัั– ะฟะปะตะนะปะธัั‚ะธ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ {0}", + "headers": "ID:,ะงะฐั:,ะะฐะทะฒะฐ:,ะขั€ะตะบะธ:", + "footer": "ะ’ะฒะตะดะธั‚ะต /playlist play [ะฟะปะตะนะปะธัั‚], ั‡ั‚ะพะฑั‹ ะดะพะฑะฐะฒะธั‚ัŒ ะฟะปะตะนะปะธัั‚ ะฒ ะพั‡ะตั€ะตะดัŒ.", + "detailTitle": "ะŸะตั€ะตะณะปัะด ะฟะปะตะนะปะธัั‚ั–ะฒ", + "detailDesc": "```ะ†ะผ'ั: {0} [{1}]\nะฃััŒะพะณะพ ั‚ั€ะตะบั–ะฒ: {2}\nะ’ะปะฐัะฝะธะบ: {3}\nะขะธะฟ: {4}\n```", + "permsValue": "๐Ÿ“– ะงะธั‚ะฐะฝะฝั: โœ“ โœ๐Ÿฝ ะ—ะฐะฟะธั: {0} ๐Ÿ—‘๏ธ ะ’ะธะดะฐะปะตะฝะฝั: {1}", + "permsValue2": "๐Ÿ“– ะงะธั‚ะฐะฝะฝั: {0}", + "trackList": "ะขั€ะตะบะธ", + "footer2": "ะ—ะฐะณะฐะปัŒะฝะฐ ั‚ั€ะธะฒะฐะปั–ัั‚ัŒ: {0}", + "playlistSelect": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะฟะปะตะนะปะธัั‚ ะดะปั ะฟะตั€ะตะณะปัะดัƒ ..." + }, + "errors": { + "notFound": "ะŸะปะตะนะปะธัั‚ [`{0}`] ะฝะต ะทะฝะฐะนะดะตะฝะพ. ะ’ะฒะตะดั–ั‚ัŒ /playlist view, ั‰ะพะฑ ะฟะพะดะธะฒะธั‚ะธัั ะฒัั– ะฒะฐัˆั– ะฟะปะตะนะปะธัั‚ะธ.", + "noAccess": "ะ’ะธะฑะฐั‡ั‚ะต! ะฃ ะฒะฐั ะฝะตะผะฐั” ะดะพัั‚ัƒะฟัƒ ะดะพ ั†ัŒะพะณะพ ะฟะปะตะนะปะธัั‚ะฐ!", + "noTrack": "ะ’ะธะฑะฐั‡ั‚ะต! ะฃ ะฟะปะตะนะปะธัั‚ั– [`{0}`] ะฝะตะผะฐั” ั‚ั€ะตะบั–ะฒ.", + "notAllowed": "ะฆั ะบะพะผะฐะฝะดะฐ ะฝะต ะดะพะทะฒะพะปะตะฝะฐ ะดะปั ะฟะพะฒ'ัะทะฐะฝะธั… ั– ะทะฐะณะฐะปัŒะฝะธั… ะฟะปะตะนะปะธัั‚ั–ะฒ.", + "nameOverLimit": "ะ’ะธะฑะฐั‡ั‚ะต! ะ†ะผ'ั ะฟะปะตะนะปะธัั‚ะฐ ะฝะต ะผะพะถะต ะฟะตั€ะตะฒะธั‰ัƒะฒะฐั‚ะธ 10 ัะธะผะฒะพะปั–ะฒ.", + "sameName": "ะ’ะธะฑะฐั‡ั‚ะต! ะฆะต ั–ะผ'ั ะฝะต ะผะพะถะต ะทะฑั–ะณะฐั‚ะธัั ะท ะฒะฐัˆะธะผ ะฝะพะฒะธะผ ั–ะผ'ัะผ.", + "deleteDefault": "ะ’ะธ ะฝะต ะผะพะถะตั‚ะต ะฒะธะดะฐะปะธั‚ะธ ะฟะปะตะนะปะธัั‚ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ.", + "limitReached": "ะ’ะธ ะฝะต ะผะพะถะตั‚ะต ัั‚ะฒะพั€ัŽะฒะฐั‚ะธ ะฑั–ะปัŒัˆะต `{0}` ะฟะปะตะนะปะธัั‚ั–ะฒ!", + "exists": "ะŸะปะตะนะปะธัั‚ [`{0}`] ะฒะถะต ั–ัะฝัƒั”.", + "invalidUrl": "ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฒะฒะตะดั–ั‚ัŒ ะดั–ะนัะฝะต ะฟะพัะธะปะฐะฝะฝั ะฐะฑะพ ะฟัƒะฑะปั–ั‡ะฝะต ะฟะปะตะนะปะธัั‚-ะฟะพัะธะปะฐะฝะฝั ะฝะฐ Spotify ะฐะฑะพ YouTube.", + "trackLimitReached": "ะ’ะธ ะดะพััะณะปะธ ะปั–ะผั–ั‚ัƒ! ะ’ะธ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ ั‚ั–ะปัŒะบะธ `{0}` ะฟั–ัะตะฝัŒ ะดะพ ัะฒะพะณะพ ะฟะปะตะนะปะธัั‚ะฐ.", + "playlistLinkNotAllowed": "ะ’ะฐะผ ะฝะต ะดะพะทะฒะพะปะตะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฟะพัะธะปะฐะฝะฝั ะฝะฐ ะฟะปะตะนะปะธัั‚.", + "streamNotAllowed": "ะ’ะฐะผ ะฝะต ะดะพะทะฒะพะปะตะฝะพ ะดะพะดะฐะฒะฐั‚ะธ ะฟะพั‚ะพะบะพะฒั– ะฒั–ะดะตะพ ัƒ ัะฒั–ะน ะฟะปะตะนะปะธัั‚.", + "positionNotFound": "ะะต ะฒะดะฐั”ั‚ัŒัั ะทะฝะฐะนั‚ะธ ะฟะพะทะธั†ั–ัŽ `{0}` ัƒ ะฒะฐัˆะพะผัƒ ะฟะปะตะนะปะธัั‚ั– [`{1}`]!", + "streaming": "ะ’ะฐะผ ะฝะต ะดะพะทะฒะพะปะตะฝะพ ะดะพะดะฐะฒะฐั‚ะธ ะฟะพั‚ะพะบะพะฒั– ะฒั–ะดะตะพ ะฒ ะฟะปะตะนะปะธัั‚!", + "track": "ะกั‚ะฐะปะฐัั ะฟะพะผะธะปะบะฐ ะฟั–ะด ั‡ะฐั ะดะพะดะฐะฒะฐะฝะฝั ั‚ั€ะตะบั–ะฒ ัƒ ะฟะปะตะนะปะธัั‚!", + "trackLimited": "ะ’ะธ ะดะพััะณะปะธ ะปั–ะผั–ั‚ัƒ! ะ’ะธ ะผะพะถะตั‚ะต ะดะพะดะฐั‚ะธ ั‚ั–ะปัŒะบะธ {0} ะฟั–ัะฝั– ะดะพ ัะฒะพะณะพ ะฟะปะตะนะปะธัั‚ะฐ.", + "trackRepeated": "ะขะฐะบะธะน ัะฐะผะธะน ั‚ั€ะตะบ ัƒะถะต ั” ัƒ ะฒะฐัˆะพะผัƒ ะฟะปะตะนะปะธัั‚ั–!" + }, + "actions": { + "play": "ะ”ะพะดะฐะฝะพ ะฟะปะตะนะปะธัั‚ [`{0}`] ะท `{1}` ะฟั–ัะฝัะผะธ ะฒ ั‡ะตั€ะณัƒ.", + "removed": "ะ’ะธ ะฒะธะดะฐะปะธะปะธ ะฟะปะตะนะปะธัั‚ [`{0}`].", + "created": "ะ’ะธ ัั‚ะฒะพั€ะธะปะธ ะฟะปะตะนะปะธัั‚ `{0}`. ะ’ะฒะตะดั–ั‚ัŒ /playlist view ะดะปั ะพั‚ั€ะธะผะฐะฝะฝั ะดะพะดะฐั‚ะบะพะฒะพั— ั–ะฝั„ะพั€ะผะฐั†ั–ั—.", + "renamed": "ะ’ะธ ะฟะตั€ะตะนะผะตะฝัƒะฒะฐะปะธ `{0}` ะฝะฐ `{1}`.", + "trackRemoved": "๐Ÿ‘‹ ะ’ะธะดะฐะปะตะฝะพ **{0}** ะท ะฟะปะตะนะปะธัั‚ะฐ {1} [`{2}`].", + "cleared": "ะ’ะธ ัƒัะฟั–ัˆะฝะพ ะพั‡ะธัั‚ะธะปะธ ัะฒั–ะน ะฟะปะตะนะปะธัั‚ [`{0}`].", + "trackAdded": "โค๏ธ ะ”ะพะดะฐะฝะพ **{0}** ะดะพ ะฟะปะตะนะปะธัั‚ะฐ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ {1} [`{2}`]!" + }, + "sharing": { + "sendErrorPlayer": "ะ’ะธะฑะฐั‡ั‚ะต! ะ’ะธ ะฝะต ะผะพะถะตั‚ะต ะฝะฐะดั–ัะปะฐั‚ะธ ะทะฐะฟั€ะพัˆะตะฝะฝั ัะฐะผะพะผัƒ ัะพะฑั–.", + "sendErrorBot": "ะ’ะธะฑะฐั‡ั‚ะต! ะ’ะธ ะฝะต ะผะพะถะตั‚ะต ะฝะฐะดั–ัะปะฐั‚ะธ ะทะฐะฟั€ะพัˆะตะฝะฝั ะฑะพั‚ัƒ.", + "belongs": "ะ’ะธะฑะฐั‡ั‚ะต! ะฆะตะน ะฟะปะตะนะปะธัั‚ ะฝะฐะปะตะถะธั‚ัŒ <@{0}>.", + "alreadyShared": "ะ’ะธะฑะฐั‡ั‚ะต! ะฆะธะผ ะฟะปะตะนะปะธัั‚ะพะผ ัƒะถะต ะฟะพะดั–ะปะธะปะธัั ะท {0}.", + "alreadySent": "ะ’ะธะฑะฐั‡ั‚ะต! ะ’ะธ ะฒะถะต ะฝะฐะดั–ัะปะฐะปะธ ะทะฐะฟั€ะพัˆะตะฝะฝั ั€ะฐะฝั–ัˆะต.", + "noAccount": "{0} ะฝะต ัั‚ะฒะพั€ะธะฒ ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะฟะปะตะนะปะธัั‚ะฐ.", + "invitationSent": "ะ—ะฐะฟั€ะพัˆะตะฝะฝั ะฝะฐะดั–ัะปะฐะฝะพ {0}." + }, + "inbox": { + "full": "ะ’ะธะฑะฐั‡ั‚ะต! ะŸะพัˆั‚ะพะฒะฐ ัะบั€ะธะฝัŒะบะฐ {0} ะฟะตั€ะตะฟะพะฒะฝะตะฝะฐ.", + "noMessages": "ะฃ ะฒะฐัˆั–ะน ะฟะพัˆั‚ะพะฒั–ะน ัะบั€ะธะฝัŒั†ั– ะฝะตะผะฐั” ะฟะพะฒั–ะดะพะผะปะตะฝัŒ." + } + }, + "search": { + "noLinkSupport": "ะšะพะผะฐะฝะดะฐ ะฟะพัˆัƒะบัƒ ะฝะต ะฟั–ะดั‚ั€ะธะผัƒั” ะฟะพัะธะปะฐะฝะฝั!", + "title": "ะŸะพัˆัƒะบ: {0}", + "desc": "โžฅ ะŸะปะฐั‚ั„ะพั€ะผะฐ: {0} **{1}**\nโžฅ ะ ะตะทัƒะปัŒั‚ะฐั‚ะธ: **{2}**\n\n{3}", + "wait": "ะ’ะธะฑะตั€ั–ั‚ัŒ ะฟั–ัะฝัŽ, ัะบัƒ ั…ะพั‡ะตั‚ะต ะดะพะดะฐั‚ะธ ะฒ ั‡ะตั€ะณัƒ.", + "success": "ะŸั–ัะฝัŽ ะดะพะดะฐะฝะพ ะฒ ั‡ะตั€ะณัƒ." + }, + "voting": { + "voted": "ะ’ะธ ะฟั€ะพะณะพะปะพััƒะฒะฐะปะธ!" + }, + "time": { + "formatError": "ะะตะฟั€ะฐะฒะธะปัŒะฝะธะน ั„ะพั€ะผะฐั‚ ั‡ะฐััƒ. ะŸั€ะธะบะปะฐะด: 2:42", + "invalidStartTime": "ะะตะดั–ะนัะฝะตะฝะธะน ั‡ะฐั ะฟะพั‡ะฐั‚ะบัƒ! ะงะฐั ะผะฐั” ะฑัƒั‚ะธ ะฒ ะผะตะถะฐั… `00:00` ั‚ะฐ `{0}`.", + "invalidEndTime": "ะะตะดั–ะนัะฝะตะฝะธะน ั‡ะฐั ะทะฐะบั–ะฝั‡ะตะฝะฝั! ะงะฐั ะผะฐั” ะฑัƒั‚ะธ ะฒ ะผะตะถะฐั… `00:00` ั‚ะฐ `{0}`." + }, + "lyrics": { + "notFound": "ะขะตะบัั‚ ะฟั–ัะฝั– ะฝะต ะทะฝะฐะนะดะตะฝะพ. ะ’ะฒะตะดั–ั‚ัŒ /lyrics <ะะฐะทะฒะฐ ะฟั–ัะฝั–> <ะะฒั‚ะพั€> ะดะปั ะฟะพัˆัƒะบัƒ ั‚ะตะบัั‚ัƒ." + }, + "queue": { + "management": { + "title": "ะะฐัั‚ัƒะฟะฝะธะน ัƒ ั‡ะตั€ะทั–:", + "cleared": "ะžั‡ะธั‰ะตะฝะพ ะฒัั– ั‚ั€ะตะบะธ ะฒ `{0}`", + "removed": "ะ’ะธะดะฐะปะตะฝะพ `{0}` ั‚ั€ะตะบั–ะฒ ั–ะท ั‡ะตั€ะณะธ.", + "swapped": "ะขั€ะตะบะธ `{0}` ั– `{1}` ะพะฑะผั–ะฝัะฝั– ะผั–ัั†ัะผะธ", + "moved": "ะขั€ะตะบ `{0}` ะทะผั–ั‰ะตะฝะธะน ะฝะฐ `{1}`" + }, + "view": { + "historyTitle": "ะ†ัั‚ะพั€ั–ั ั‡ะตั€ะณะธ:", + "title": "ะŸะพั‚ะพั‡ะฝะฐ ั‡ะตั€ะณะฐ", + "desc": "**ะ—ะฐั€ะฐะทะพะผ ะณั€ะฐั”: [*ะฟะพัะธะปะฐะฝะฝั*]({0}) โฎฏ**\n{1}" + }, + "errors": { + "queueFull": "ะ’ะธะฑะฐั‡ั‚ะต, ะฒะธ ะดะพััะณะปะธ ะผะฐะบัะธะผะฐะปัŒะฝะพั— ะบั–ะปัŒะบะพัั‚ั– `{0}` ั‚ั€ะตะบั–ะฒ ัƒ ั‡ะตั€ะทั–!", + "outOfList": "ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฝะฐะดะฐะนั‚ะต ะดั–ะนัะฝะธะน ั–ะฝะดะตะบั ั‚ั€ะตะบัƒ!", + "duplicateTrack": "ะ’ะธะฑะฐั‡ั‚ะต, ั†ะตะน ั‚ั€ะตะบ ัƒะถะต ั” ะฒ ั‡ะตั€ะทั–." + } + }, + "songRequest": { + "channelCreated": "ะšะฐะฝะฐะป ะดะปั ะทะฐะฟะธั‚ั–ะฒ ะฟั–ัะตะฝัŒ ({0}) ัั‚ะฒะพั€ะตะฝะพ! ะ’ะธ ะผะพะถะตั‚ะต ะฟะพั‡ะฐั‚ะธ ะทะฐะฟะธั‚ัƒะฒะฐั‚ะธ ะฑัƒะดัŒ-ัะบัƒ ะฟั–ัะฝัŽ ะทะฐ ะฝะฐะทะฒะพัŽ ะฐะฑะพ URL ัƒ ั†ัŒะพะผัƒ ะบะฐะฝะฐะปั– ะฑะตะท ะฝะตะพะฑั…ั–ะดะฝะพัั‚ั– ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฟั€ะตั„ั–ะบั ะฑะพั‚ะฐ." + }, + "pagination": { + "prev": "ะะฐะทะฐะด", + "next": "ะ”ะฐะปั–", + "play": "ะ“ั€ะฐั‚ะธ", + "share": "ะŸะพะดั–ะปะธั‚ะธัั", + "export": "ะ•ะบัะฟะพั€ั‚ัƒะฒะฐั‚ะธ", + "delete": "ะ’ะธะดะฐะปะธั‚ะธ", + "page": { + "title": "ะ’ะฒะตะดั–ั‚ัŒ ะฝะพะผะตั€ ัั‚ะพั€ั–ะฝะบะธ", + "field": "ะะพะผะตั€ ัั‚ะพั€ั–ะฝะบะธ" + } + } +} diff --git a/langs/VN.json b/langs/VN.json index 6fef020..78e4612 100644 --- a/langs/VN.json +++ b/langs/VN.json @@ -1,172 +1,262 @@ { - "unknownException": "โš ๏ธ ฤรฃ xแบฃy ra lแป—i khi chแบกy lแป‡nh! Vui lรฒng thแปญ lแบกi sau hoแบทc vร o server Discord cแปงa chรบng tรดi ฤ‘แปƒ ฤ‘ฦฐแปฃc hแป— trแปฃ.", - "enabled": "ฤ‘รฃ bแบญt", - "disabled": "ฤ‘รฃ tแบฏt", - "nodeReconnect": "Vui lรฒng thแปญ lแบกi! Sau khi node kแบฟt nแป‘i lแบกi.", - "noChannel": "Khรดng cรณ kรชnh voice ฤ‘แปƒ kแบฟt nแป‘i. Vui lรฒng cung cแบฅp mแป™t kรชnh hoแบทc tham gia mแป™t kรชnh.", - "alreadyConnected": "ฤรฃ kแบฟt nแป‘i vแป›i kรชnh voice.", - "noPermission": "Xin lแป—i! Tรดi khรดng cรณ quyแปn tham gia hoแบทc nรณi trong kรชnh voice cแปงa bแบกn.", - "noCreatePermission": "Xin lแป—i! Tรดi khรดng cรณ quyแปn tแบกo kรชnh yรชu cแบงu bร i hรกt.", - "noPlaySource": "Khรดng thแปƒ tรฌm thแบฅy nguแป“n phรกt nร o!", - "noPlayer": "Khรดng tรฌm thแบฅy trรฌnh phรกt nร o trรชn mรกy chแปง nร y.", - "notVote": "Lแป‡nh nร y yรชu cแบงu bแบกn bรฌnh chแปn! Gรต `/vote` ฤ‘แปƒ biแบฟt thรชm thรดng tin.", - "missingIntents": "Xin lแป—i, lแป‡nh nร y khรดng thแปƒ thแปฑc hiแป‡n vรฌ bot thiแบฟu quyแปn yรชu cแบงu cแบงn thiแบฟt: `({0})`.", - "languageNotFound": "Khรดng tรฌm thแบฅy gรณi ngรดn ngแปฏ! Vui lรฒng chแปn mแป™t gรณi ngรดn ngแปฏ hiแป‡n cรณ.", - "changedLanguage": "ฤรฃ thay ฤ‘แป•i thร nh cรดng sang gรณi ngรดn ngแปฏ `{0}`.", - "setPrefix": "Hoร n thร nh! Tiแปn tแป‘ cแปงa tรดi trong mรกy chแปง cแปงa bแบกn bรขy giแป lร  `{0}`. Thแปญ chแบกy `{1}ping` ฤ‘แปƒ kiแปƒm tra.", - "setDJ": "ฤแบทt DJ thร nh {0}.", - "setQueue": "ฤแบทt chแบฟ ฤ‘แป™ hร ng ฤ‘แปฃi thร nh `{0}`.", - "247": "Chแบฟ ฤ‘แป™ 24/7 bรขy giแป lร  `{0}`.", - "bypassVote": "Bแบกn cรณ hแป‡ thแป‘ng bรฌnh chแปn `{0}`.", - "setVolume": "ฤแบทt รขm lฦฐแปฃng thร nh `{0}`%", - "toggleController": "Bรขy giแป bแบกn ฤ‘รฃ `{0}` bแป™ ฤ‘iแปu khiแปƒn nhแบกc.", - "toggleDuplicateTrack": "Bรขy giแป bแบกn ฤ‘รฃ `{0}` tรญnh nฤƒng ngฤƒn chแบทn bร i hรกt trรนng lแบทp.", - "toggleControllerMsg": "Bรขy giแป bแบกn ฤ‘รฃ `{0}` tin nhแบฏn tแปซ bแป™ ฤ‘iแปu khiแปƒn nhแบกc.", - "toggleSilentMsg": "Bแบกn cรณ {0} tin nhแบฏn im lแบทng.", - "settingsMenu": "Cร i ฤแบทt Mรกy Chแปง | {0}", - "settingsTitle": "โค๏ธ Thรดng Tin Cฦก Bแบฃn:", - "settingsValue": "```Tiแปn tแป‘: {0}\nNgรดn ngแปฏ: {1}\nBแป™ ฤiแปu Khiแปƒn Nhแบกc: {2}\nVai Trรฒ DJ: @{3}\nBแป Qua Bรฌnh Chแปn: {4}\n24/7: {5}\nร‚m Lฦฐแปฃng Mแบทc ฤแป‹nh: {6}%\nThแปi Gian Phรกt: {7}```", - "settingsTitle2": "๐Ÿ”— Thรดng tin hร ng ฤ‘แปฃi:", - "settingsValue2": "```Chแบฟ ฤแป™ Hร ng ฤแปฃi: {0}\nSแป‘ Bร i Tแป‘i ฤa: {1}\nBร i Hรกt Trรนng Lแบทp: {2}```", - "settingsTitle3": "๐ŸŽค Thรดng Tin Trแบกng Thรกi Voice:", - "settingsPermTitle": "โœจ Quyแปn hแบกn:", - "settingsPermValue": "```{0} Quแบฃn Trแป‹ Viรชn\n{1} Quแบฃn_Lรฝ_Guild\n{2} Quแบฃn_Lรฝ_Kรชnh\n{3} Quแบฃn_Lรฝ_Tin_Nhแบฏn```", - "pingTitle1": "Thรดng Tin Bot:", - "pingTitle2": "Thรดng Tin Trรฌnh Phรกt:", - "pingField1": "```ID Shard: {0}/{1}\nฤแป™ Trแป… Shard: {2:.3f}s {3}\nKhu Vแปฑc: {4}```", - "pingField2": "```Node: {0} - {1:.3f}s\nTrรฌnh Phรกt: {2}\nKhu Vแปฑc Voice: {3}```", - "addEffect": "ฤรฃ รกp dแปฅng hiแป‡u แปฉng `{0}`.", - "clearEffect": "Cรกc hiแป‡u แปฉng รขm thanh ฤ‘รฃ ฤ‘ฦฐแปฃc xรณa!", - "filterTagAlreadyInUse": "Hiแป‡u แปฉng รขm thanh nร y ฤ‘รฃ ฤ‘ฦฐแปฃc sแปญ dแปฅng! Vui lรฒng sแปญ dแปฅng /cleareffect ฤ‘แปƒ xรณa nรณ.", - "playlistViewTitle": "๐Ÿ“œ Tแบฅt Cแบฃ Playlist Cแปงa {0}", - "playlistViewHeaders": "ID:,Thแปi Gian:,Tรชn:,Bร i Hรกt:", - "playlistFooter": "Gรต /playlist play [playlist] ฤ‘แปƒ thรชm playlist vร o hร ng ฤ‘แปฃi.", - "playlistNotFound": "Khรดng tรฌm thแบฅy playlist [`{0}`]. Gรต /playlist view ฤ‘แปƒ xem tแบฅt cแบฃ playlist cแปงa bแบกn.", - "playlistNotAccess": "Xin lแป—i! Bแบกn khรดng ฤ‘ฦฐแปฃc phรฉp truy cแบญp playlist nร y!", - "playlistNoTrack": "Xin lแป—i! Khรดng cรณ bร i hรกt nร o trong playlist [`{0}`].", - "playlistNotAllow": "Lแป‡nh nร y khรดng ฤ‘ฦฐแปฃc phรฉp trรชn playlist ฤ‘ฦฐแปฃc liรชn kแบฟt vร  chia sแบป.", - "playlistPlay": "ฤรฃ thรชm playlist [`{0}`] vแป›i `{1}` bร i hรกt vร o hร ng ฤ‘แปฃi.", - "playlistOverText": "Xin lแป—i! Tรชn playlist khรดng thแปƒ vฦฐแปฃt quรก 10 kรฝ tแปฑ.", - "playlistSameName": "Xin lแป—i! Vui lรฒng chแปn tรชn mแป›i cho playlist.", - "playlistDeleteError": "Bแบกn khรดng ฤ‘ฦฐแปฃc phรฉp xรณa playlist mแบทc ฤ‘แป‹nh.", - "playlistRemove": "Bแบกn ฤ‘รฃ xรณa playlist [`{0}`].", - "playlistSendErrorPlayer": "Xin lแป—i! Bแบกn khรดng thแปƒ gแปญi lแปi mแปi cho chรญnh mรฌnh.", - "playlistSendErrorBot": "Xin lแป—i! Bแบกn khรดng thแปƒ gแปญi lแปi mแปi cho mแป™t bot.", - "playlistBelongs": "Xin lแป—i! Playlist nร y thuแป™c vแป <@{0}>.", - "playlistShare": "Xin lแป—i! Playlist nร y ฤ‘รฃ ฤ‘ฦฐแปฃc chia sแบป vแป›i {0}.", - "playlistSent": "Xin lแป—i! Bแบกn ฤ‘รฃ gแปญi lแปi mแปi cho ngฦฐแปi dรนng ฤ‘รณ trฦฐแป›c ฤ‘รณ rแป“i.", - "noPlaylistAcc": "{0} khรดng tแบกo tร i khoแบฃn playlist.", - "overPlaylistCreation": "Bแบกn khรดng thแปƒ tแบกo nhiแปu hฦกn `{0}` playlist!", - "playlistExists": "Playlist [`{0}`] ฤ‘รฃ tแป“n tแบกi.", - "playlistNotInvalidUrl": "Vui lรฒng nhแบญp liรชn kแบฟt playlist Spotify hoแบทc Youtube cรดng khai hแปฃp lแป‡.", - "playlistCreated": "Bแบกn ฤ‘รฃ tแบกo playlist `{0}`. Gรต /playlist view ฤ‘แปƒ biแบฟt thรชm thรดng tin.", - "playlistRenamed": "Bแบกn ฤ‘รฃ ฤ‘แป•i tรชn `{0}` thร nh `{1}`.", - "playlistLimitTrack": "Bแบกn ฤ‘รฃ ฤ‘แบกt giแป›i hแบกn! Bแบกn chแป‰ cรณ thแปƒ thรชm `{0}` bร i hรกt vร o playlist cแปงa mรฌnh.", - "playlistPlaylistLink": "Bแบกn khรดng ฤ‘ฦฐแปฃc phรฉp sแปญ dแปฅng liรชn kแบฟt playlist.", - "playlistStream": "Bแบกn khรดng ฤ‘ฦฐแปฃc phรฉp thรชm video trแปฑc tiแบฟp vร o playlist cแปงa mรฌnh.", - "playlistPositionNotFound": "Khรดng thแปƒ tรฌm thแบฅy vแป‹ trรญ `{0}` tแปซ playlist [`{1}`] cแปงa bแบกn!", - "playlistRemoved": "๐Ÿ‘‹ ฤรฃ xรณa **{0}** khแปi playlist [`{2}`] cแปงa {1}.", - "playlistClear": "Bแบกn ฤ‘รฃ xรณa thร nh cรดng playlist [`{0}`] cแปงa mรฌnh.", - "playlistView": "Trรฌnh Xem Playlist", - "playlistViewDesc": "```Tรชn | ID: {0} | {1}\nTแป•ng Sแป‘ Bร i Hรกt: {2}\nChแปง SแปŸ Hแปฏu: {3}\nLoแบกi: {4}\n```", - "playlistViewPermsValue": "๐Ÿ“– ฤแปc: โœ“ โœ๐Ÿฝ Viแบฟt: {0} ๐Ÿ—‘๏ธ Xรณa: {1}", - "playlistViewPermsValue2": "๐Ÿ“– ฤแปc: {0}", - "playlistViewTrack": "Bร i Hรกt", - "playlistViewPage": "Trang: {0}/{1} | Tแป•ng Thแปi Lฦฐแปฃng: {2}", - "inboxFull": "Xin lแป—i! Hแป™p thฦฐ cแปงa {0} ฤ‘รฃ ฤ‘แบงy.", - "inboxNoMsg": "Khรดng cรณ tin nhแบฏn nร o trong hแป™p thฦฐ cแปงa bแบกn.", - "invitationSent": "ฤรฃ gแปญi lแปi mแปi ฤ‘แบฟn {0}.", - "notInChannel": "{0}, bแบกn phแบฃi แปŸ trong {1} ฤ‘แปƒ sแปญ dแปฅng lแป‡nh voice. Vui lรฒng tham gia lแบกi nแบฟu bแบกn ฤ‘ang แปŸ trong voice!", - "noTrackPlaying": "Hiแป‡n tแบกi khรดng cรณ bร i hรกt nร o ฤ‘ang phรกt", - "noTrackFound": "Khรดng tรฌm thแบฅy bร i hรกt nร o vแป›i truy vแบฅn ฤ‘รณ! Vui lรฒng cung cแบฅp url hแปฃp lแป‡.", - "noLinkSupport": "Lแป‡nh tรฌm kiแบฟm khรดng hแป— trแปฃ liรชn kแบฟt!", - "voted": "Bแบกn ฤ‘รฃ bรฌnh chแปn!", - "missingPosPerm": "Chแป‰ DJ hoแบทc quแบฃn trแป‹ viรชn mแป›i cรณ thแปƒ thay ฤ‘แป•i vแป‹ trรญ.", - "missingModePerm": "Chแป‰ DJ hoแบทc quแบฃn trแป‹ viรชn mแป›i cรณ thแปƒ chuyแปƒn chแบฟ ฤ‘แป™ lแบทp.", - "missingQueuePerm": "Chแป‰ DJ hoแบทc quแบฃn trแป‹ viรชn mแป›i cรณ thแปƒ xรณa bร i hรกt khแปi hร ng ฤ‘แปฃi.", - "missingAutoPlayPerm": "Chแป‰ DJ hoแบทc quแบฃn trแป‹ viรชn mแป›i cรณ thแปƒ bแบญt hoแบทc tแบฏt chแบฟ ฤ‘แป™ tแปฑ ฤ‘แป™ng phรกt!", - "missingFunctionPerm": "Chแป‰ DJ hoแบทc Admin mแป›i cรณ thแปƒ sแปญ dแปฅng chแปฉc nฤƒng nร y.", - "timeFormatError": "ฤแป‹nh dแบกng thแปi gian khรดng chรญnh xรกc. Vรญ dแปฅ: 2:42 hoแบทc 12:39:31", - "lyricsNotFound": "Khรดng tรฌm thแบฅy lแปi bร i hรกt. Gรต /lyrics ฤ‘แปƒ tรฌm lแปi bร i hรกt.", - "missingTrackInfo": "Mแป™t sแป‘ chi tiแบฟt bร i hรกt bแป‹ thiแบฟu.", - "noVoiceChannel": "Khรดng Tรฌm Thแบฅy Kรชnh Voice!", - "playlistAddError": "Bแบกn khรดng ฤ‘ฦฐแปฃc phรฉp thรชm video trแปฑc tiแบฟp vร o playlist cแปงa mรฌnh!", - "playlistAddError2": "ฤรฃ xแบฃy ra sแปฑ cแป‘ khi thรชm bร i hรกt vร o playlist!", - "playlistLimited": "Bแบกn ฤ‘รฃ ฤ‘แบกt giแป›i hแบกn! Bแบกn chแป‰ cรณ thแปƒ thรชm {0} bร i hรกt vร o playlist cแปงa mรฌnh.", - "playlistRepeated": "Bร i hรกt nร y ฤ‘รฃ cรณ trong playlist cแปงa bแบกn!", - "playlistAdded": "โค๏ธ ฤรฃ thรชm **{0}** vร o playlist [`{2}`] cแปงa {1}!", - "playerDropdown": "Chแปn mแป™t bร i hรกt ฤ‘แปƒ bแป qua ฤ‘แบฟn ...", - "playerFilter": "Chแปn mแป™t bแป™ lแปc ฤ‘แปƒ รกp dแปฅng ...", - "buttonBack": "Quay Lแบกi", - "buttonPause": "Tแบกm Dแปซng", - "buttonResume": "Tiแบฟp Tแปฅc", - "buttonSkip": "Bแป Qua", - "buttonLeave": "Rแปi Khแปi", - "buttonLoop": "Lแบทp Lแบกi", - "buttonVolumeUp": "Tฤƒng ร‚m Lฦฐแปฃng", - "buttonVolumeDown": "Giแบฃm ร‚m Lฦฐแปฃng", - "buttonVolumeMute": "Tแบฏt Tiแบฟng", - "buttonVolumeUnmute": "Bแบญt Tiแบฟng", - "buttonAutoPlay": "Tแปฑ ฤแป™ng Phรกt", - "buttonShuffle": "Trแป™n Bร i", - "buttonForward": "Tua Tแป›i", - "buttonRewind": "Tua Lรนi", - "buttonLyrics": "Lแปi Bร i Hรกt", - "nowplayingDesc": "**ฤang Phรกt:**\n```{0}```", - "nowplayingField": "Tiแบฟp Theo:", - "nowplayingLink": "Nghe trรชn {0}", - "connect": "ฤรฃ kแบฟt nแป‘i vแป›i {0}", - "live": "TRแปฐC TIแบพP", - "playlistLoad": " ๐ŸŽถ ฤรฃ thรชm playlist **{0}** vแป›i `{1}` bร i hรกt vร o hร ng ฤ‘แปฃi.", - "trackLoad": "ฤรฃ thรชm **[{0}](<{1}>)** bแปŸi **{2}** (`{3}`) ฤ‘แปƒ bแบฏt ฤ‘แบงu phรกt.\n", - "trackLoad_pos": "ฤรฃ thรชm **[{0}](<{1}>)** bแปŸi **{2}** (`{3}`) vร o hร ng ฤ‘แปฃi แปŸ vแป‹ trรญ **{4}**\n", - "searchTitle": "Truy Vแบฅn Tรฌm Kiแบฟm: {0}", - "searchDesc": "โžฅ Nแปn tแบฃng: {0} **{1}**\nโžฅ Kแบฟt quแบฃ: **{2}**\n\n{3}", - "searchWait": "Chแปn bร i hรกt bแบกn muแป‘n thรชm vร o hร ng ฤ‘แปฃi.", - "searchTimeout": "Tรฌm kiแบฟm hแบฟt thแปi gian. Vui lรฒng thแปญ lแบกi sau.", - "searchSuccess": "ฤรฃ thรชm bร i hรกt vร o hร ng ฤ‘แปฃi.", - "queueTitle": "Hร ng ฤแปฃi Sแบฏp Tแป›i:", - "historyTitle": "Lแป‹ch Sแปญ Hร ng ฤแปฃi:", - "viewTitle": "Hร ng ฤแปฃi Nhแบกc", - "viewDesc": "**ฤang Phรกt: [Nhแบฅp Vร o ฤรขy]({0}) โฎฏ**\n{1}", - "viewFooter": "Trang: {0}/{1} | Tแป•ng Thแปi Lฦฐแปฃng: {2}", - "pauseError": "Trรฌnh phรกt ฤ‘รฃ ฤ‘ฦฐแปฃc tแบกm dแปซng.", - "pauseVote": "{0} ฤ‘รฃ bรฌnh chแปn tแบกm dแปซng bร i hรกt. [{1}/{2}]", - "paused": "`{0}` ฤ‘รฃ tแบกm dแปซng trรฌnh phรกt.", - "resumeError": "Trรฌnh phรกt khรดng bแป‹ tแบกm dแปซng.", - "resumeVote": "{0} ฤ‘รฃ bรฌnh chแปn tiแบฟp tแปฅc bร i hรกt. [{1}/{2}]", - "resumed": "`{0}` ฤ‘รฃ tiแบฟp tแปฅc trรฌnh phรกt.", - "shuffleError": "Thรชm nhiแปu bร i hรกt vร o hร ng ฤ‘แปฃi trฦฐแป›c khi trแป™n bร i.", - "shuffleVote": "{0} ฤ‘รฃ bรฌnh chแปn trแป™n hร ng ฤ‘แปฃi. [{1}/{2}]", - "shuffled": "Hร ng ฤ‘แปฃi ฤ‘รฃ ฤ‘ฦฐแปฃc trแป™n.", - "skipError": "Khรดng cรณ bร i hรกt nร o ฤ‘แปƒ bแป qua.", - "skipVote": "{0} ฤ‘รฃ bรฌnh chแปn bแป qua bร i hรกt. [{1}/{2}]", - "skipped": "`{0}` ฤ‘รฃ bแป qua bร i hรกt.", - "backVote": "{0} ฤ‘รฃ bรฌnh chแปn quay lแบกi bร i hรกt trฦฐแป›c. [{1}/{2}]", - "backed": "`{0}` ฤ‘ang quay lแบกi bร i hรกt trฦฐแป›c.", - "leaveVote": "{0} ฤ‘รฃ bรฌnh chแปn dแปซng trรฌnh phรกt. [{1}/{2}]", - "left": "`{0}` ฤ‘รฃ dแปซng trรฌnh phรกt.", - "seek": "ฤแบทt trรฌnh phรกt thร nh **{0}**", - "repeat": "Chแบฟ ฤ‘แป™ lแบทp lแบกi ฤ‘รฃ ฤ‘ฦฐแปฃc ฤ‘แบทt thร nh `{0}`", - "cleared": "ฤรฃ xรณa tแบฅt cแบฃ bร i hรกt trong `{0}`", - "removed": "`{0}` bร i hรกt ฤ‘รฃ ฤ‘ฦฐแปฃc xรณa khแปi hร ng ฤ‘แปฃi.", - "forward": "Tua tแป›i trรฌnh phรกt ฤ‘แบฟn **{0}**", - "rewind": "Tua lรนi trรฌnh phรกt ฤ‘แบฟn **{0}**", - "replay": "Phรกt lแบกi bร i hรกt hiแป‡n tแบกi.", - "swapped": "`{0}` vร  `{1}` ฤ‘รฃ ฤ‘ฦฐแปฃc hoรกn ฤ‘แป•i", - "moved": "ฤรฃ di chuyแปƒn `{0}` ฤ‘แบฟn `{1}`", - "autoplay": "Chแบฟ ฤ‘แป™ tแปฑ ฤ‘แป™ng phรกt bรขy giแป lร  **{0}**", - "notdj": "Bแบกn khรดng phแบฃi lร  DJ. DJ hiแป‡n tแบกi lร  {0}.", - "djToMe": "Bแบกn khรดng thแปƒ chuyแปƒn vai trรฒ DJ cho chรญnh mรฌnh hoแบทc mแป™t bot.", - "djNotInChannel": "`{0}` khรดng แปŸ trong kรชnh voice.", - "djswap": "Bแบกn ฤ‘รฃ chuyแปƒn vai trรฒ DJ cho `{0}`.", - "voicelinkQueueFull": "Xin lแป—i, bแบกn ฤ‘รฃ ฤ‘แบกt tแป‘i ฤ‘a `{0}` bร i hรกt trong hร ng ฤ‘แปฃi!", - "voicelinkOutofList": "Vui lรฒng cung cแบฅp chแป‰ sแป‘ bร i hรกt hแปฃp lแป‡!", - "voicelinkDuplicateTrack": "Xin lแป—i, bร i hรกt nร y ฤ‘รฃ cรณ trong hร ng ฤ‘แปฃi.", - "decodeError": "ฤรฃ xแบฃy ra lแป—i khi giแบฃi mรฃ tแป‡p!", - "invalidStartTime": "Thแปi gian bแบฏt ฤ‘แบงu khรดng hแปฃp lแป‡, phแบฃi nแบฑm giแปฏa `00:00` vร  `{0}`", - "invalidEndTime": "Thแปi gian kแบฟt thรบc khรดng hแปฃp lแป‡, phแบฃi nแบฑm giแปฏa `00:00` vร  `{0}`", - "invalidTimeOrder": "Thแปi gian kแบฟt thรบc khรดng thแปƒ nhแป hฦกn hoแบทc bแบฑng thแปi gian bแบฏt ฤ‘แบงu", - "setStageAnnounceTemplate": "Hoร n thร nh! Tแปซ bรขy giแป, trแบกng thรกi voice nhฦฐ cรกi bแบกn ฤ‘ang แปŸ sแบฝ ฤ‘ฦฐแปฃc ฤ‘แบทt tรชn theo mแบซu cแปงa bแบกn. Bแบกn sแบฝ thแบฅy nรณ cแบญp nhแบญt trong vร i giรขy.", - "createSongRequestChannel": "Mแป™t kรชnh yรชu cแบงu bร i hรกt ({0}) ฤ‘รฃ ฤ‘ฦฐแปฃc tแบกo! Bแบกn cรณ thแปƒ bแบฏt ฤ‘แบงu yรชu cแบงu bแบฅt kแปณ bร i hรกt nร o bแบฑng tรชn hoแบทc URL trong kรชnh ฤ‘รณ, mร  khรดng cแบงn sแปญ dแปฅng tiแปn tแป‘ bot." -} \ No newline at end of file + "common": { + "errors": { + "unknown": "โš ๏ธ ฤรฃ xแบฃy ra lแป—i khi chแบกy lแป‡nh! Vui lรฒng thแปญ lแบกi sau hoแบทc vร o server Discord cแปงa chรบng tรดi ฤ‘แปƒ ฤ‘ฦฐแปฃc hแป— trแปฃ.", + "missingIntents": "Xin lแป—i, lแป‡nh nร y khรดng thแปƒ thแปฑc hiแป‡n vรฌ bot thiแบฟu quyแปn yรชu cแบงu cแบงn thiแบฟt: `({0})`." + }, + "status": { + "enabled": "ฤ‘รฃ bแบญt", + "disabled": "ฤ‘รฃ tแบฏt", + "live": "TRแปฐC TIแบพP" + } + }, + "player": { + "errors": { + "nodeReconnect": "Vui lรฒng thแปญ lแบกi! Sau khi node kแบฟt nแป‘i lแบกi.", + "noPlaySource": "Khรดng thแปƒ tรฌm thแบฅy nguแป“n phรกt nร o!", + "noPlayer": "Khรดng tรฌm thแบฅy trรฌnh phรกt nร o trรชn mรกy chแปง nร y.", + "noTrackPlaying": "Hiแป‡n tแบกi khรดng cรณ bร i hรกt nร o ฤ‘ang phรกt", + "noTrackFound": "Khรดng tรฌm thแบฅy bร i hรกt nร o vแป›i truy vแบฅn ฤ‘รณ! Vui lรฒng cung cแบฅp url hแปฃp lแป‡." + }, + "dropdown": { + "trackSelect": "Chแปn mแป™t bร i hรกt ฤ‘แปƒ bแป qua ฤ‘แบฟn ...", + "filterSelect": "Chแปn mแป™t bแป™ lแปc ฤ‘แปƒ รกp dแปฅng ..." + }, + "buttons": { + "back": "Quay Lแบกi", + "pause": "Tแบกm Dแปซng", + "resume": "Tiแบฟp Tแปฅc", + "skip": "Bแป Qua", + "leave": "Rแปi Khแปi", + "loop": "Lแบทp Lแบกi", + "volumeUp": "Tฤƒng ร‚m Lฦฐแปฃng", + "volumeDown": "Giแบฃm ร‚m Lฦฐแปฃng", + "volumeMute": "Tแบฏt Tiแบฟng", + "volumeUnmute": "Bแบญt Tiแบฟng", + "autoPlay": "Tแปฑ ฤแป™ng Phรกt", + "shuffle": "Trแป™n Bร i", + "forward": "Tua Tแป›i", + "rewind": "Tua Lรนi", + "lyrics": "Lแปi Bร i Hรกt" + }, + "playback": { + "nowplayingDesc": "**ฤang Phรกt:**\n```{0}```", + "nowplayingField": "Tiแบฟp Theo:", + "nowplayingLink": "Nghe trรชn {0}", + "playlistLoad": " ๐ŸŽถ ฤรฃ thรชm playlist **{0}** vแป›i `{1}` bร i hรกt vร o hร ng ฤ‘แปฃi.", + "trackLoad": "ฤรฃ thรชm **[{0}](<{1}>)** bแปŸi **{2}** (`{3}`) ฤ‘แปƒ bแบฏt ฤ‘แบงu phรกt.\n", + "trackLoadPos": "ฤรฃ thรชm **[{0}](<{1}>)** bแปŸi **{2}** (`{3}`) vร o hร ng ฤ‘แปฃi แปŸ vแป‹ trรญ **{4}**\n" + }, + "controls": { + "pause": { + "error": "Trรฌnh phรกt ฤ‘รฃ ฤ‘ฦฐแปฃc tแบกm dแปซng.", + "vote": "{0} ฤ‘รฃ bรฌnh chแปn tแบกm dแปซng bร i hรกt. [{1}/{2}]", + "success": "`{0}` ฤ‘รฃ tแบกm dแปซng trรฌnh phรกt." + }, + "resume": { + "error": "Trรฌnh phรกt khรดng bแป‹ tแบกm dแปซng.", + "vote": "{0} ฤ‘รฃ bรฌnh chแปn tiแบฟp tแปฅc bร i hรกt. [{1}/{2}]", + "success": "`{0}` ฤ‘รฃ tiแบฟp tแปฅc trรฌnh phรกt." + }, + "shuffle": { + "error": "Thรชm nhiแปu bร i hรกt vร o hร ng ฤ‘แปฃi trฦฐแป›c khi trแป™n bร i.", + "vote": "{0} ฤ‘รฃ bรฌnh chแปn trแป™n hร ng ฤ‘แปฃi. [{1}/{2}]", + "success": "Hร ng ฤ‘แปฃi ฤ‘รฃ ฤ‘ฦฐแปฃc trแป™n." + }, + "skip": { + "error": "Khรดng cรณ bร i hรกt nร o ฤ‘แปƒ bแป qua.", + "vote": "{0} ฤ‘รฃ bรฌnh chแปn bแป qua bร i hรกt. [{1}/{2}]", + "success": "`{0}` ฤ‘รฃ bแป qua bร i hรกt." + }, + "back": { + "vote": "{0} ฤ‘รฃ bรฌnh chแปn quay lแบกi bร i hรกt trฦฐแป›c. [{1}/{2}]", + "success": "`{0}` ฤ‘ang quay lแบกi bร i hรกt trฦฐแป›c." + }, + "leave": { + "vote": "{0} ฤ‘รฃ bรฌnh chแปn dแปซng trรฌnh phรกt. [{1}/{2}]", + "success": "`{0}` ฤ‘รฃ dแปซng trรฌnh phรกt." + }, + "seek": "ฤแบทt trรฌnh phรกt thร nh **{0}**", + "repeat": "Chแบฟ ฤ‘แป™ lแบทp lแบกi ฤ‘รฃ ฤ‘ฦฐแปฃc ฤ‘แบทt thร nh `{0}`", + "forward": "Tua tแป›i trรฌnh phรกt ฤ‘แบฟn **{0}**", + "rewind": "Tua lรนi trรฌnh phรกt ฤ‘แบฟn **{0}**", + "replay": "Phรกt lแบกi bร i hรกt hiแป‡n tแบกi.", + "autoplay": "Chแบฟ ฤ‘แป™ tแปฑ ฤ‘แป™ng phรกt bรขy giแป lร  **{0}**" + } + }, + "voice": { + "connection": { + "noChannel": "Khรดng cรณ kรชnh voice ฤ‘แปƒ kแบฟt nแป‘i. Vui lรฒng cung cแบฅp mแป™t kรชnh hoแบทc tham gia mแป™t kรชnh.", + "alreadyConnected": "ฤรฃ kแบฟt nแป‘i vแป›i kรชnh voice.", + "noPermission": "Xin lแป—i! Tรดi khรดng cรณ quyแปn tham gia hoแบทc nรณi trong kรชnh voice cแปงa bแบกn.", + "notInChannel": "{0}, bแบกn phแบฃi แปŸ trong {1} ฤ‘แปƒ sแปญ dแปฅng lแป‡nh voice. Vui lรฒng tham gia lแบกi nแบฟu bแบกn ฤ‘ang แปŸ trong voice!", + "connect": "ฤรฃ kแบฟt nแป‘i vแป›i {0}" + }, + "stageChannel": { + "setAnnounceTemplate": "Hoร n thร nh! Tแปซ bรขy giแป, trแบกng thรกi voice nhฦฐ cรกi bแบกn ฤ‘ang แปŸ sแบฝ ฤ‘ฦฐแปฃc ฤ‘แบทt tรชn theo mแบซu cแปงa bแบกn. Bแบกn sแบฝ thแบฅy nรณ cแบญp nhแบญt trong vร i giรขy." + } + }, + "permissions": { + "noCreatePermission": "Xin lแป—i! Tรดi khรดng cรณ quyแปn tแบกo kรชnh yรชu cแบงu bร i hรกt.", + "missingPosition": "Chแป‰ DJ hoแบทc quแบฃn trแป‹ viรชn mแป›i cรณ thแปƒ thay ฤ‘แป•i vแป‹ trรญ.", + "missingMode": "Chแป‰ DJ hoแบทc quแบฃn trแป‹ viรชn mแป›i cรณ thแปƒ chuyแปƒn chแบฟ ฤ‘แป™ lแบทp.", + "missingQueue": "Chแป‰ DJ hoแบทc quแบฃn trแป‹ viรชn mแป›i cรณ thแปƒ xรณa bร i hรกt khแปi hร ng ฤ‘แปฃi.", + "missingAutoPlay": "Chแป‰ DJ hoแบทc quแบฃn trแป‹ viรชn mแป›i cรณ thแปƒ bแบญt hoแบทc tแบฏt chแบฟ ฤ‘แป™ tแปฑ ฤ‘แป™ng phรกt!", + "missingFunction": "Chแป‰ DJ hoแบทc Admin mแป›i cรณ thแปƒ sแปญ dแปฅng chแปฉc nฤƒng nร y.", + "notDj": "Bแบกn khรดng phแบฃi lร  DJ. DJ hiแป‡n tแบกi lร  {0}.", + "djToSelf": "Bแบกn khรดng thแปƒ chuyแปƒn vai trรฒ DJ cho chรญnh mรฌnh hoแบทc mแป™t bot.", + "djNotInChannel": "`{0}` khรดng แปŸ trong kรชnh voice.", + "djSwapped": "Bแบกn ฤ‘รฃ chuyแปƒn vai trรฒ DJ cho `{0}`." + }, + "settings": { + "actions": { + "languageNotFound": "Khรดng tรฌm thแบฅy gรณi ngรดn ngแปฏ! Vui lรฒng chแปn mแป™t gรณi ngรดn ngแปฏ hiแป‡n cรณ.", + "languageChanged": "ฤรฃ thay ฤ‘แป•i thร nh cรดng sang gรณi ngรดn ngแปฏ `{0}`.", + "prefixSet": "Hoร n thร nh! Tiแปn tแป‘ cแปงa tรดi trong mรกy chแปง cแปงa bแบกn bรขy giแป lร  `{0}`. Thแปญ chแบกy `{1}ping` ฤ‘แปƒ kiแปƒm tra.", + "djSet": "ฤแบทt DJ thร nh {0}.", + "queueModeSet": "ฤแบทt chแบฟ ฤ‘แป™ hร ng ฤ‘แปฃi thร nh `{0}`.", + "mode247": "Chแบฟ ฤ‘แป™ 24/7 bรขy giแป lร  `{0}`.", + "bypassVote": "Bแบกn cรณ hแป‡ thแป‘ng bรฌnh chแปn `{0}`.", + "volumeSet": "ฤแบทt รขm lฦฐแปฃng thร nh `{0}`%", + "controllerToggled": "Bรขy giแป bแบกn ฤ‘รฃ `{0}` bแป™ ฤ‘iแปu khiแปƒn nhแบกc.", + "duplicateTrackToggled": "Bรขy giแป bแบกn ฤ‘รฃ `{0}` tรญnh nฤƒng ngฤƒn chแบทn bร i hรกt trรนng lแบทp.", + "controllerMsgToggled": "Bรขy giแป bแบกn ฤ‘รฃ `{0}` tin nhแบฏn tแปซ bแป™ ฤ‘iแปu khiแปƒn nhแบกc.", + "silentMsgToggled": "Bแบกn cรณ {0} tin nhแบฏn im lแบทng." + }, + "menu": "Cร i ฤแบทt Mรกy Chแปง | {0}", + "basic": { + "title": "โค๏ธ Thรดng Tin Cฦก Bแบฃn:", + "value": "```Tiแปn tแป‘: {0}\nNgรดn ngแปฏ: {1}\nBแป™ ฤiแปu Khiแปƒn Nhแบกc: {2}\nVai Trรฒ DJ: @{3}\nBแป Qua Bรฌnh Chแปn: {4}\n24/7: {5}\nร‚m Lฦฐแปฃng Mแบทc ฤแป‹nh: {6}%\nThแปi Gian Phรกt: {7}```" + }, + "queue": { + "title": "๐Ÿ”— Thรดng tin hร ng ฤ‘แปฃi:", + "value": "```Chแบฟ ฤแป™ Hร ng ฤแปฃi: {0}\nSแป‘ Bร i Tแป‘i ฤa: {1}\nBร i Hรกt Trรนng Lแบทp: {2}```" + }, + "voice": { + "title": "๐ŸŽค Thรดng Tin Trแบกng Thรกi Voice:" + }, + "permissions": { + "title": "โœจ Quyแปn hแบกn:", + "value": "```{0} Quแบฃn Trแป‹ Viรชn\n{1} Quแบฃn_Lรฝ_Guild\n{2} Quแบฃn_Lรฝ_Kรชnh\n{3} Quแบฃn_Lรฝ_Tin_Nhแบฏn```" + } + }, + "ping": { + "title1": "Thรดng Tin Bot:", + "title2": "Thรดng Tin Trรฌnh Phรกt:", + "field1": "```ID Shard: {0}/{1}\nฤแป™ Trแป… Shard: {2:.3f}s {3}\nKhu Vแปฑc: {4}```", + "field2": "```Node: {0} - {1:.3f}s\nTrรฌnh Phรกt: {2}\nKhu Vแปฑc Voice: {3}```" + }, + "effects": { + "added": "ฤรฃ รกp dแปฅng hiแป‡u แปฉng `{0}`.", + "cleared": "Cรกc hiแป‡u แปฉng รขm thanh ฤ‘รฃ ฤ‘ฦฐแปฃc xรณa!", + "tagInUse": "Hiแป‡u แปฉng รขm thanh nร y ฤ‘รฃ ฤ‘ฦฐแปฃc sแปญ dแปฅng! Vui lรฒng sแปญ dแปฅng /cleareffect ฤ‘แปƒ xรณa nรณ." + }, + "playlist": { + "view": { + "title": "Tแบฅt Cแบฃ Playlist Cแปงa {0}", + "headers": "ID:,Thแปi Gian:,Tรชn:,Bร i Hรกt:", + "footer": "Gรต /playlist play [playlist] ฤ‘แปƒ thรชm playlist vร o hร ng ฤ‘แปฃi.", + "detailTitle": "Trรฌnh Xem Playlist", + "detailDesc": "```Tรชn: {0} [{1}]\nTแป•ng Sแป‘ Bร i Hรกt: {2}\nChแปง SแปŸ Hแปฏu: {3}\nLoแบกi: {4}\n```", + "permsValue": "๐Ÿ“– ฤแปc: โœ“ โœ๐Ÿฝ Viแบฟt: {0} ๐Ÿ—‘๏ธ Xรณa: {1}", + "permsValue2": "๐Ÿ“– ฤแปc: {0}", + "trackList": "Bร i Hรกt", + "footer2": "Tแป•ng thแปi gian: {0}", + "playlistSelect": "Chแปn mแป™t danh sรกch phรกt ฤ‘แปƒ xem ..." + }, + "errors": { + "notFound": "Khรดng tรฌm thแบฅy playlist [`{0}`]. Gรต /playlist view ฤ‘แปƒ xem tแบฅt cแบฃ playlist cแปงa bแบกn.", + "noAccess": "Xin lแป—i! Bแบกn khรดng ฤ‘ฦฐแปฃc phรฉp truy cแบญp playlist nร y!", + "noTrack": "Xin lแป—i! Khรดng cรณ bร i hรกt nร o trong playlist [`{0}`].", + "notAllowed": "Lแป‡nh nร y khรดng ฤ‘ฦฐแปฃc phรฉp trรชn playlist ฤ‘ฦฐแปฃc liรชn kแบฟt vร  chia sแบป.", + "nameOverLimit": "Xin lแป—i! Tรชn playlist khรดng thแปƒ vฦฐแปฃt quรก 10 kรฝ tแปฑ.", + "sameName": "Xin lแป—i! Vui lรฒng chแปn tรชn mแป›i cho playlist.", + "deleteDefault": "Bแบกn khรดng ฤ‘ฦฐแปฃc phรฉp xรณa playlist mแบทc ฤ‘แป‹nh.", + "limitReached": "Bแบกn khรดng thแปƒ tแบกo nhiแปu hฦกn `{0}` playlist!", + "exists": "Playlist [`{0}`] ฤ‘รฃ tแป“n tแบกi.", + "invalidUrl": "Vui lรฒng nhแบญp liรชn kแบฟt playlist Spotify hoแบทc Youtube cรดng khai hแปฃp lแป‡.", + "trackLimitReached": "Bแบกn ฤ‘รฃ ฤ‘แบกt giแป›i hแบกn! Bแบกn chแป‰ cรณ thแปƒ thรชm `{0}` bร i hรกt vร o playlist cแปงa mรฌnh.", + "playlistLinkNotAllowed": "Bแบกn khรดng ฤ‘ฦฐแปฃc phรฉp sแปญ dแปฅng liรชn kแบฟt playlist.", + "streamNotAllowed": "Bแบกn khรดng ฤ‘ฦฐแปฃc phรฉp thรชm video trแปฑc tiแบฟp vร o playlist cแปงa mรฌnh.", + "positionNotFound": "Khรดng thแปƒ tรฌm thแบฅy vแป‹ trรญ `{0}` tแปซ playlist [`{1}`] cแปงa bแบกn!", + "streaming": "Bแบกn khรดng ฤ‘ฦฐแปฃc phรฉp thรชm video trแปฑc tiแบฟp vร o playlist cแปงa mรฌnh!", + "track": "ฤรฃ xแบฃy ra sแปฑ cแป‘ khi thรชm bร i hรกt vร o playlist!", + "trackLimited": "Bแบกn ฤ‘รฃ ฤ‘แบกt giแป›i hแบกn! Bแบกn chแป‰ cรณ thแปƒ thรชm {0} bร i hรกt vร o playlist cแปงa mรฌnh.", + "trackRepeated": "Bร i hรกt nร y ฤ‘รฃ cรณ trong playlist cแปงa bแบกn!" + }, + "actions": { + "play": "ฤรฃ thรชm playlist [`{0}`] vแป›i `{1}` bร i hรกt vร o hร ng ฤ‘แปฃi.", + "removed": "Bแบกn ฤ‘รฃ xรณa playlist [`{0}`].", + "created": "Bแบกn ฤ‘รฃ tแบกo playlist `{0}`. Gรต /playlist view ฤ‘แปƒ biแบฟt thรชm thรดng tin.", + "renamed": "Bแบกn ฤ‘รฃ ฤ‘แป•i tรชn `{0}` thร nh `{1}`.", + "trackRemoved": "๐Ÿ‘‹ ฤรฃ xรณa **{0}** khแปi playlist [`{2}`] cแปงa {1}.", + "cleared": "Bแบกn ฤ‘รฃ xรณa thร nh cรดng playlist [`{0}`] cแปงa mรฌnh.", + "trackAdded": "โค๏ธ ฤรฃ thรชm **{0}** vร o playlist [`{2}`] cแปงa {1}!" + }, + "sharing": { + "sendErrorPlayer": "Xin lแป—i! Bแบกn khรดng thแปƒ gแปญi lแปi mแปi cho chรญnh mรฌnh.", + "sendErrorBot": "Xin lแป—i! Bแบกn khรดng thแปƒ gแปญi lแปi mแปi cho mแป™t bot.", + "belongs": "Xin lแป—i! Playlist nร y thuแป™c vแป <@{0}>.", + "alreadyShared": "Xin lแป—i! Playlist nร y ฤ‘รฃ ฤ‘ฦฐแปฃc chia sแบป vแป›i {0}.", + "alreadySent": "Xin lแป—i! Bแบกn ฤ‘รฃ gแปญi lแปi mแปi cho ngฦฐแปi dรนng ฤ‘รณ trฦฐแป›c ฤ‘รณ rแป“i.", + "noAccount": "{0} khรดng tแบกo tร i khoแบฃn playlist.", + "invitationSent": "ฤรฃ gแปญi lแปi mแปi ฤ‘แบฟn {0}." + }, + "inbox": { + "full": "Xin lแป—i! Hแป™p thฦฐ cแปงa {0} ฤ‘รฃ ฤ‘แบงy.", + "noMessages": "Khรดng cรณ tin nhแบฏn nร o trong hแป™p thฦฐ cแปงa bแบกn." + } + }, + "search": { + "noLinkSupport": "Lแป‡nh tรฌm kiแบฟm khรดng hแป— trแปฃ liรชn kแบฟt!", + "title": "Truy Vแบฅn Tรฌm Kiแบฟm: {0}", + "desc": "โžฅ Nแปn tแบฃng: {0} **{1}**\nโžฅ Kแบฟt quแบฃ: **{2}**\n\n{3}", + "wait": "Chแปn bร i hรกt bแบกn muแป‘n thรชm vร o hร ng ฤ‘แปฃi.", + "success": "ฤรฃ thรชm bร i hรกt vร o hร ng ฤ‘แปฃi." + }, + "voting": { + "voted": "Bแบกn ฤ‘รฃ bรฌnh chแปn!" + }, + "time": { + "formatError": "ฤแป‹nh dแบกng thแปi gian khรดng chรญnh xรกc. Vรญ dแปฅ: 2:42 hoแบทc 12:39:31", + "invalidStartTime": "Thแปi gian bแบฏt ฤ‘แบงu khรดng hแปฃp lแป‡, phแบฃi nแบฑm giแปฏa `00:00` vร  `{0}`", + "invalidEndTime": "Thแปi gian kแบฟt thรบc khรดng hแปฃp lแป‡, phแบฃi nแบฑm giแปฏa `00:00` vร  `{0}`" + }, + "lyrics": { + "notFound": "Khรดng tรฌm thแบฅy lแปi bร i hรกt. Gรต /lyrics ฤ‘แปƒ tรฌm lแปi bร i hรกt." + }, + "queue": { + "management": { + "title": "Hร ng ฤแปฃi Sแบฏp Tแป›i:", + "cleared": "ฤรฃ xรณa tแบฅt cแบฃ bร i hรกt trong `{0}`", + "removed": "`{0}` bร i hรกt ฤ‘รฃ ฤ‘ฦฐแปฃc xรณa khแปi hร ng ฤ‘แปฃi.", + "swapped": "`{0}` vร  `{1}` ฤ‘รฃ ฤ‘ฦฐแปฃc hoรกn ฤ‘แป•i", + "moved": "ฤรฃ di chuyแปƒn `{0}` ฤ‘แบฟn `{1}`" + }, + "view": { + "historyTitle": "Lแป‹ch Sแปญ Hร ng ฤแปฃi:", + "title": "Hร ng ฤแปฃi Nhแบกc", + "desc": "**ฤang Phรกt: [Nhแบฅp Vร o ฤรขy]({0}) โฎฏ**\n{1}" + }, + "errors": { + "queueFull": "Xin lแป—i, bแบกn ฤ‘รฃ ฤ‘แบกt tแป‘i ฤ‘a `{0}` bร i hรกt trong hร ng ฤ‘แปฃi!", + "outOfList": "Vui lรฒng cung cแบฅp chแป‰ sแป‘ bร i hรกt hแปฃp lแป‡!", + "duplicateTrack": "Xin lแป—i, bร i hรกt nร y ฤ‘รฃ cรณ trong hร ng ฤ‘แปฃi." + } + }, + "songRequest": { + "channelCreated": "Mแป™t kรชnh yรชu cแบงu bร i hรกt ({0}) ฤ‘รฃ ฤ‘ฦฐแปฃc tแบกo! Bแบกn cรณ thแปƒ bแบฏt ฤ‘แบงu yรชu cแบงu bแบฅt kแปณ bร i hรกt nร o bแบฑng tรชn hoแบทc URL trong kรชnh ฤ‘รณ, mร  khรดng cแบงn sแปญ dแปฅng tiแปn tแป‘ bot." + }, + "pagination": { + "prev": "Trฦฐแป›c", + "next": "Tiแบฟp", + "play": "Phรกt", + "share": "Chia sแบป", + "export": "Xuแบฅt", + "delete": "Xรณa", + "page": { + "title": "Nhแบญp sแป‘ trang", + "field": "Sแป‘ trang" + } + } +} diff --git a/langs/ZHCN.json b/langs/ZHCN.json index 4aae7f3..57f6e82 100644 --- a/langs/ZHCN.json +++ b/langs/ZHCN.json @@ -1,172 +1,262 @@ { - "unknownException": "โš ๏ธ ๆ‰ง่กŒๅ‘ฝไปคๆ—ถๅ‡บ้”™๏ผ่ฏท็จๅŽๅ†่ฏ•๏ผŒๆˆ–ๅŠ ๅ…ฅๆˆ‘ไปฌ็š„ Discord ๆœๅŠกๅ™จ่Žทๅ–ๆ”ฏๆŒใ€‚", - "enabled": "ๅทฒๅฏ็”จ", - "disabled": "ๅทฒ็ฆ็”จ", - "nodeReconnect": "่ฏทๅœจ่Š‚็‚น้‡่ฟžๅŽๅ†่ฏ•๏ผ", - "noChannel": "ๆœชๆฃ€ๆต‹ๅˆฐ่ฏญ้Ÿณ้ข‘้“๏ผŒ่ฏทๆไพ›ไธ€ไธชๆˆ–ๅŠ ๅ…ฅ่ฏญ้Ÿณ้ข‘้“ใ€‚", - "alreadyConnected": "ๅทฒ่ฟžๆŽฅๅˆฐ่ฏญ้Ÿณ้ข‘้“ใ€‚", - "noPermission": "ๆŠฑๆญ‰๏ผๆˆ‘ๆฒกๆœ‰ๆƒ้™ๅŠ ๅ…ฅๆˆ–ๅœจๆ‚จ็š„่ฏญ้Ÿณ้ข‘้“ไธญๅ‘่จ€ใ€‚", - "noCreatePermission": "ๆŠฑๆญ‰๏ผๆˆ‘ๆฒกๆœ‰ๆƒ้™ๅˆ›ๅปบ็‚นๆญŒ้ข‘้“ใ€‚", - "noPlaySource": "ๆ‰พไธๅˆฐๅฏๆ’ญๆ”พ็š„่ต„ๆบ๏ผ", - "noPlayer": "่ฏฅๆœๅŠกๅ™จไธŠๆฒกๆœ‰ๆ‰พๅˆฐๆ’ญๆ”พๅ™จใ€‚", - "notVote": "่ฏฅๅ‘ฝไปค้œ€่ฆๆ‚จๅ…ˆๆŠ•็ฅจ๏ผ่พ“ๅ…ฅ `/vote` ไบ†่งฃๆ›ดๅคšใ€‚", - "missingIntents": "ๆŠฑๆญ‰๏ผŒๆ— ๆณ•ๆ‰ง่กŒๆญคๅ‘ฝไปค๏ผŒๅ› ไธบ็ผบๅฐ‘ๆ‰€้œ€็š„่ฏทๆฑ‚ intent๏ผš`({0})`ใ€‚", - "languageNotFound": "ๆœชๆ‰พๅˆฐ่ฏญ่จ€ๅŒ…๏ผ่ฏท้€‰ๆ‹ฉไธ€ไธชๅทฒๆœ‰็š„่ฏญ่จ€ๅŒ…ใ€‚", - "changedLanguage": "ๅทฒๆˆๅŠŸๅˆ‡ๆขไธบ `{0}` ่ฏญ่จ€ๅŒ…ใ€‚", - "setPrefix": "ๅฎŒๆˆ๏ผๆˆ‘ๅœจๆ‚จๆœๅŠกๅ™จ็š„ๅ‰็ผ€็Žฐๅœจๆ˜ฏ `{0}`ใ€‚ๅฐ่ฏ•่ฟ่กŒ `{1}ping` ๆต‹่ฏ•ไธ€ไธ‹ๅงใ€‚", - "setDJ": "ๅทฒๅฐ† DJ ่ฎพ็ฝฎไธบ {0}ใ€‚", - "setQueue": "ๅทฒๅฐ†้˜Ÿๅˆ—ๆจกๅผ่ฎพ็ฝฎไธบ `{0}`ใ€‚", - "247": "24/7 ๆจกๅผ็Žฐๅทฒ `{0}`ใ€‚", - "bypassVote": "ๆ‚จๆ‹ฅๆœ‰ `{0}` ๆŠ•็ฅจ็ณป็ปŸใ€‚", - "setVolume": "ๅทฒๅฐ†้Ÿณ้‡่ฎพ็ฝฎไธบ `{0}`%", - "toggleController": "ๆ‚จ็Žฐๅœจๅทฒ `{0}` ้ŸณไนๆŽงๅˆถๅ™จใ€‚", - "toggleDuplicateTrack": "ๆ‚จๅทฒ `{0}` ้‡ๅคๆ›ฒ็›ฎ้ข„้˜ฒๅŠŸ่ƒฝใ€‚", - "toggleControllerMsg": "ๆ‚จ็Žฐๅœจๅทฒ `{0}` ๆฅ่‡ช้ŸณไนๆŽงๅˆถๅ™จ็š„ๆถˆๆฏใ€‚", - "toggleSilentMsg": "ๆ‚จๅทฒ {0} ้™้ป˜ๆถˆๆฏใ€‚", - "settingsMenu": "ๆœๅŠกๅ™จ่ฎพ็ฝฎ | {0}", - "settingsTitle": "โค๏ธ ๅŸบๆœฌไฟกๆฏ:", - "settingsValue": "```ๅ‰็ผ€: {0}\n่ฏญ่จ€: {1}\n้ŸณไนๆŽงๅˆถๅ™จ: {2}\nDJ ่ง’่‰ฒ: @{3}\nๆŠ•็ฅจ่ทณ่ฟ‡: {4}\n24/7: {5}\n้ป˜่ฎค้Ÿณ้‡: {6}%\nๆ’ญๆ”พๆ—ถ้•ฟ: {7}```", - "settingsTitle2": "๐Ÿ”— ้˜Ÿๅˆ—ไฟกๆฏ:", - "settingsValue2": "```้˜Ÿๅˆ—ๆจกๅผ: {0}\nๆœ€ๅคงๆญŒๆ›ฒๆ•ฐ: {1}\n้‡ๅคๆ›ฒ็›ฎ: {2}```", - "settingsTitle3": "๐ŸŽค ่ฏญ้Ÿณ็Šถๆ€ไฟกๆฏ:", - "settingsPermTitle": "โœจ ๆƒ้™:", - "settingsPermValue": "```{0} ็ฎก็†ๅ‘˜\n{1} ็ฎก็†ๆœๅŠกๅ™จ\n{2} ็ฎก็†้ข‘้“\n{3} ็ฎก็†ๆถˆๆฏ```", - "pingTitle1": "ๆœบๅ™จไบบไฟกๆฏ:", - "pingTitle2": "ๆ’ญๆ”พๅ™จไฟกๆฏ:", - "pingField1": "```ๅˆ†็‰‡ ID: {0}/{1}\nๅˆ†็‰‡ๅปถ่ฟŸ: {2:.3f}s {3}\nๅŒบๅŸŸ: {4}```", - "pingField2": "```่Š‚็‚น: {0} - {1:.3f}s\nๆ’ญๆ”พๅ™จๆ•ฐ: {2}\n่ฏญ้ŸณๅŒบๅŸŸ: {3}```", - "addEffect": "ๅทฒๅบ”็”จ `{0}` ้Ÿณๆ•ˆใ€‚", - "clearEffect": "้Ÿณๆ•ˆๅทฒๆธ…้™ค๏ผ", - "filterTagAlreadyInUse": "่ฏฅ้Ÿณๆ•ˆๅทฒๅœจไฝฟ็”จไธญ๏ผ่ฏทไฝฟ็”จ /cleareffect <ๆ ‡็ญพ> ็งป้™คใ€‚", - "playlistViewTitle": "๐Ÿ“œ {0} ็š„ๆ‰€ๆœ‰ๆ’ญๆ”พๅˆ—่กจ", - "playlistViewHeaders": "ID:,ๆ—ถ้•ฟ:,ๅ็งฐ:,ๆ›ฒ็›ฎๆ•ฐ:", - "playlistFooter": "่พ“ๅ…ฅ /playlist play [ๆ’ญๆ”พๅˆ—่กจ] ๅฐ†ๅ…ถๆทปๅŠ ๅˆฐ้˜Ÿๅˆ—ไธญใ€‚", - "playlistNotFound": "ๆœชๆ‰พๅˆฐๆ’ญๆ”พๅˆ—่กจ [`{0}`]ใ€‚่พ“ๅ…ฅ /playlist view ๆŸฅ็œ‹ๆ‰€ๆœ‰ๆ’ญๆ”พๅˆ—่กจใ€‚", - "playlistNotAccess": "ๆŠฑๆญ‰๏ผๆ‚จๆ— ๆƒ่ฎฟ้—ฎๆญคๆ’ญๆ”พๅˆ—่กจ๏ผ", - "playlistNoTrack": "ๆŠฑๆญ‰๏ผๆ’ญๆ”พๅˆ—่กจ [`{0}`] ไธญๆฒกๆœ‰ๆ›ฒ็›ฎใ€‚", - "playlistNotAllow": "่ฏฅๅ‘ฝไปคไธๆ”ฏๆŒๅทฒๅ…ณ่”ๆˆ–ๅ…ฑไบซ็š„ๆ’ญๆ”พๅˆ—่กจใ€‚", - "playlistPlay": "ๅทฒๅฐ†ๆ’ญๆ”พๅˆ—่กจ [`{0}`] ็š„ `{1}` ้ฆ–ๆญŒๆทปๅŠ ๅˆฐ้˜Ÿๅˆ—ไธญใ€‚", - "playlistOverText": "ๆŠฑๆญ‰๏ผๆ’ญๆ”พๅˆ—่กจๅ็งฐไธ่ƒฝ่ถ…่ฟ‡ 10 ไธชๅญ—็ฌฆใ€‚", - "playlistSameName": "ๆŠฑๆญ‰๏ผ่ฏทไธบๆ’ญๆ”พๅˆ—่กจ้€‰ๆ‹ฉไธ€ไธชๆ–ฐๅๅญ—ใ€‚", - "playlistDeleteError": "ไธๅ…่ฎธๅˆ ้™ค้ป˜่ฎคๆ’ญๆ”พๅˆ—่กจใ€‚", - "playlistRemove": "ๆ‚จๅทฒๅˆ ้™คๆ’ญๆ”พๅˆ—่กจ [`{0}`]ใ€‚", - "playlistSendErrorPlayer": "ๆŠฑๆญ‰๏ผๆ‚จไธ่ƒฝ็ป™่‡ชๅทฑๅ‘้€้‚€่ฏทใ€‚", - "playlistSendErrorBot": "ๆŠฑๆญ‰๏ผๆ‚จไธ่ƒฝ็ป™ๆœบๅ™จไบบๅ‘้€้‚€่ฏทใ€‚", - "playlistBelongs": "ๆŠฑๆญ‰๏ผๆญคๆ’ญๆ”พๅˆ—่กจๅฑžไบŽ <@{0}>ใ€‚", - "playlistShare": "ๆŠฑๆญ‰๏ผๆญคๆ’ญๆ”พๅˆ—่กจๅทฒไธŽ {0} ๅ…ฑไบซใ€‚", - "playlistSent": "ๆŠฑๆญ‰๏ผๆ‚จไน‹ๅ‰ๅทฒ็ป็ป™่ฏฅ็”จๆˆทๅ‘้€่ฟ‡้‚€่ฏทใ€‚", - "noPlaylistAcc": "{0} ่ฟ˜ๆฒกๆœ‰ๅˆ›ๅปบๆ’ญๆ”พๅˆ—่กจ่ดฆๅทใ€‚", - "overPlaylistCreation": "ๆ‚จๆœ€ๅคšๅช่ƒฝๅˆ›ๅปบ `{0}` ไธชๆ’ญๆ”พๅˆ—่กจ๏ผ", - "playlistExists": "ๆ’ญๆ”พๅˆ—่กจ [`{0}`] ๅทฒๅญ˜ๅœจใ€‚", - "playlistNotInvalidUrl": "่ฏท่พ“ๅ…ฅๆœ‰ๆ•ˆ็š„ๅ…ฌๅ…ฑ Spotify ๆˆ– YouTube ๆ’ญๆ”พๅˆ—่กจ้“พๆŽฅใ€‚", - "playlistCreated": "ๆ‚จๅทฒๅˆ›ๅปบ `{0}` ๆ’ญๆ”พๅˆ—่กจใ€‚่พ“ๅ…ฅ /playlist view ๆŸฅ็œ‹่ฏฆๆƒ…ใ€‚", - "playlistRenamed": "ๆ‚จๅทฒๅฐ† `{0}` ้‡ๅ‘ฝๅไธบ `{1}`ใ€‚", - "playlistLimitTrack": "ๅทฒ่พพๅˆฐไธŠ้™๏ผๆฏไธชๆ’ญๆ”พๅˆ—่กจๆœ€ๅคšๅช่ƒฝๆทปๅŠ  `{0}` ้ฆ–ๆญŒใ€‚", - "playlistPlaylistLink": "ไธๅ…่ฎธไฝฟ็”จๆ’ญๆ”พๅˆ—่กจ้“พๆŽฅใ€‚", - "playlistStream": "ไธๅ…่ฎธๅฐ†ๆตๅช’ไฝ“่ง†้ข‘ๆทปๅŠ ๅˆฐๆ’ญๆ”พๅˆ—่กจใ€‚", - "playlistPositionNotFound": "ๅœจๆ’ญๆ”พๅˆ—่กจ [`{1}`] ไธญๆ‰พไธๅˆฐไฝ็ฝฎ `{0}`๏ผ", - "playlistRemoved": "๐Ÿ‘‹ ๅทฒไปŽ {1} ็š„ๆ’ญๆ”พๅˆ—่กจ [`{2}`] ไธญ็งป้™ค **{0}**ใ€‚", - "playlistClear": "ๆ‚จๅทฒๆˆๅŠŸๆธ…็ฉบๆ’ญๆ”พๅˆ—่กจ [`{0}`]ใ€‚", - "playlistView": "ๆ’ญๆ”พๅˆ—่กจๆŸฅ็œ‹ๅ™จ", - "playlistViewDesc": "```ๅ็งฐ | ID: {0} | {1}\nๆ€ปๆ›ฒ็›ฎ: {2}\nๆ‹ฅๆœ‰่€…: {3}\n็ฑปๅž‹: {4}\n```", - "playlistViewPermsValue": "๐Ÿ“– ่ฏปๅ–: โœ“ โœ๐Ÿฝ ๅ†™ๅ…ฅ: {0} ๐Ÿ—‘๏ธ ๅˆ ้™ค: {1}", - "playlistViewPermsValue2": "๐Ÿ“– ่ฏปๅ–: {0}", - "playlistViewTrack": "ๆ›ฒ็›ฎๅˆ—่กจ", - "playlistViewPage": "็ฌฌ {0}/{1} ้กต | ๆ€ปๆ—ถ้•ฟ: {2}", - "inboxFull": "ๆŠฑๆญ‰๏ผ{0} ็š„ๆ”ถไปถ็ฎฑๅทฒๆปกใ€‚", - "inboxNoMsg": "ๆ‚จ็š„ๆ”ถไปถ็ฎฑไธญๆฒกๆœ‰ๆถˆๆฏใ€‚", - "invitationSent": "้‚€่ฏทๅทฒๅ‘้€็ป™ {0}ใ€‚", - "notInChannel": "{0}๏ผŒๆ‚จๅฟ…้กปๅœจ {1} ๆ‰่ƒฝไฝฟ็”จ่ฏญ้Ÿณๅ‘ฝไปคใ€‚ๅฆ‚ๆžœๆ‚จๅทฒๅœจ้ข‘้“๏ผŒ่ฏท้‡ๆ–ฐๅŠ ๅ…ฅ๏ผ", - "noTrackPlaying": "ๅฝ“ๅ‰ๆฒกๆœ‰ๆญฃๅœจๆ’ญๆ”พ็š„ๆญŒๆ›ฒ", - "noTrackFound": "ๆœชๆ‰พๅˆฐๅฏนๅบ”็š„ๆญŒๆ›ฒ๏ผŒ่ฏทๆไพ›ๆœ‰ๆ•ˆ้“พๆŽฅใ€‚", - "noLinkSupport": "ๆœ็ดขๅ‘ฝไปคไธๆ”ฏๆŒ้“พๆŽฅ๏ผ", - "voted": "ๆ‚จๅทฒๆŠ•็ฅจ๏ผ", - "missingPosPerm": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ‘˜ๆ‰่ƒฝๆ›ดๆ”นไฝ็ฝฎใ€‚", - "missingModePerm": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ‘˜ๆ‰่ƒฝๅˆ‡ๆขๅพช็Žฏๆจกๅผใ€‚", - "missingQueuePerm": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ‘˜ๆ‰่ƒฝไปŽ้˜Ÿๅˆ—ไธญ็งป้™คๆ›ฒ็›ฎใ€‚", - "missingAutoPlayPerm": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ‘˜ๆ‰่ƒฝๅฏ็”จๆˆ–็ฆ็”จ่‡ชๅŠจๆ’ญๆ”พ๏ผ", - "missingFunctionPerm": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ‘˜ๆ‰่ƒฝไฝฟ็”จๆญคๅŠŸ่ƒฝใ€‚", - "timeFormatError": "ๆ—ถ้—ดๆ ผๅผ้”™่ฏฏใ€‚็คบไพ‹: 2:42 ๆˆ– 12:39:31", - "lyricsNotFound": "ๆœชๆ‰พๅˆฐๆญŒ่ฏใ€‚่พ“ๅ…ฅ /lyrics <ๆญŒๆ›ฒๅ> <ไฝœ่€…> ๆŸฅๆ‰พๆญŒ่ฏใ€‚", - "missingTrackInfo": "้ƒจๅˆ†ๆ›ฒ็›ฎไฟกๆฏ็ผบๅคฑใ€‚", - "noVoiceChannel": "ๆœชๆ‰พๅˆฐ่ฏญ้Ÿณ้ข‘้“๏ผ", - "playlistAddError": "ไธๅ…่ฎธๅฐ†ๆตๅช’ไฝ“่ง†้ข‘ๆทปๅŠ ๅˆฐๆ’ญๆ”พๅˆ—่กจ๏ผ", - "playlistAddError2": "ๆทปๅŠ ๆ›ฒ็›ฎๅˆฐๆ’ญๆ”พๅˆ—่กจๆ—ถๅ‡บ็Žฐ้—ฎ้ข˜๏ผ", - "playlistLimited": "ๅทฒ่พพๅˆฐไธŠ้™๏ผๆ‚จๆœ€ๅคšๅช่ƒฝๅœจๆ’ญๆ”พๅˆ—่กจไธญๆทปๅŠ  {0} ้ฆ–ๆญŒใ€‚", - "playlistRepeated": "่ฏฅๆ›ฒ็›ฎๅทฒๅœจๆ’ญๆ”พๅˆ—่กจไธญ๏ผ", - "playlistAdded": "โค๏ธ ๅทฒๅฐ† **{0}** ๆทปๅŠ ๅˆฐ {1} ็š„ๆ’ญๆ”พๅˆ—่กจ [`{2}`]๏ผ", - "playerDropdown": "้€‰ๆ‹ฉ่ฆ่ทณ่ฝฌ็š„ๆญŒๆ›ฒ ...", - "playerFilter": "้€‰ๆ‹ฉ่ฆๅบ”็”จ็š„้Ÿณๆ•ˆ ...", - "buttonBack": "ไธŠไธ€้ฆ–", - "buttonPause": "ๆš‚ๅœ", - "buttonResume": "็ปง็ปญ", - "buttonSkip": "่ทณ่ฟ‡", - "buttonLeave": "้€€ๅ‡บ", - "buttonLoop": "ๅพช็Žฏ", - "buttonVolumeUp": "้Ÿณ้‡+", - "buttonVolumeDown": "้Ÿณ้‡-", - "buttonVolumeMute": "้™้Ÿณ", - "buttonVolumeUnmute": "ๅ–ๆถˆ้™้Ÿณ", - "buttonAutoPlay": "่‡ชๅŠจๆ’ญๆ”พ", - "buttonShuffle": "้šๆœบๆ’ญๆ”พ", - "buttonForward": "ๅฟซ่ฟ›", - "buttonRewind": "ๅฟซ้€€", - "buttonLyrics": "ๆญŒ่ฏ", - "nowplayingDesc": "**ๆญฃๅœจๆ’ญๆ”พ:**\n```{0}```", - "nowplayingField": "ๅณๅฐ†ๆ’ญๆ”พ:", - "nowplayingLink": "ๅœจ {0} ๆ”ถๅฌ", - "connect": "ๅทฒ่ฟžๆŽฅๅˆฐ {0}", - "live": "็›ดๆ’ญ", - "playlistLoad": " ๐ŸŽถ ๅทฒๅฐ†ๆ’ญๆ”พๅˆ—่กจ **{0}** ็š„ `{1}` ้ฆ–ๆญŒๆทปๅŠ ๅˆฐ้˜Ÿๅˆ—ไธญใ€‚", - "trackLoad": "ๅทฒๆทปๅŠ  **[{0}](<{1}>)** - **{2}** (`{3}`) ๅนถๅผ€ๅง‹ๆ’ญๆ”พใ€‚\n", - "trackLoad_pos": "ๅทฒๆทปๅŠ  **[{0}](<{1}>)** - **{2}** (`{3}`) ๅˆฐ้˜Ÿๅˆ—ไฝ็ฝฎ **{4}**\n", - "searchTitle": "ๆœ็ดขๅ…ณ้”ฎ่ฏ: {0}", - "searchDesc": "โžฅ ๅนณๅฐ: {0} **{1}**\nโžฅ ็ป“ๆžœๆ•ฐ: **{2}**\n\n{3}", - "searchWait": "่ฏท้€‰ๆ‹ฉ่ฆๆทปๅŠ ๅˆฐ้˜Ÿๅˆ—็š„ๆญŒๆ›ฒใ€‚", - "searchTimeout": "ๆœ็ดข่ถ…ๆ—ถ๏ผŒ่ฏท็จๅŽ้‡่ฏ•ใ€‚", - "searchSuccess": "ๆญŒๆ›ฒๅทฒๆทปๅŠ ๅˆฐ้˜Ÿๅˆ—ใ€‚", - "queueTitle": "ๅณๅฐ†ๆ’ญๆ”พ้˜Ÿๅˆ—:", - "historyTitle": "ๆ’ญๆ”พๅކๅฒ:", - "viewTitle": "้Ÿณไน้˜Ÿๅˆ—", - "viewDesc": "**ๆญฃๅœจๆ’ญๆ”พ: [็‚นๆˆ‘]({0}) โฎฏ**\n{1}", - "viewFooter": "็ฌฌ {0}/{1} ้กต | ๆ€ปๆ—ถ้•ฟ: {2}", - "pauseError": "ๆ’ญๆ”พๅ™จๅทฒๆš‚ๅœใ€‚", - "pauseVote": "{0} ๆŠ•็ฅจๆš‚ๅœไบ†ๆญŒๆ›ฒใ€‚[{1}/{2}]", - "paused": "`{0}` ๅทฒๆš‚ๅœๆ’ญๆ”พๅ™จใ€‚", - "resumeError": "ๆ’ญๆ”พๅ™จๆœชๆš‚ๅœใ€‚", - "resumeVote": "{0} ๆŠ•็ฅจ็ปง็ปญๆ’ญๆ”พใ€‚[{1}/{2}]", - "resumed": "`{0}` ๅทฒ็ปง็ปญๆ’ญๆ”พใ€‚", - "shuffleError": "่ฏทๅœจ้˜Ÿๅˆ—ไธญๆทปๅŠ ๆ›ดๅคšๆญŒๆ›ฒๅŽๅ†้šๆœบๆ’ญๆ”พใ€‚", - "shuffleVote": "{0} ๆŠ•็ฅจ้šๆœบๆ’ญๆ”พ้˜Ÿๅˆ—ใ€‚[{1}/{2}]", - "shuffled": "้˜Ÿๅˆ—ๅทฒ้šๆœบๆŽ’ๅบใ€‚", - "skipError": "ๆฒกๆœ‰ๆญŒๆ›ฒๅฏ่ทณ่ฟ‡ใ€‚", - "skipVote": "{0} ๆŠ•็ฅจ่ทณ่ฟ‡ๆญŒๆ›ฒใ€‚[{1}/{2}]", - "skipped": "`{0}` ๅทฒ่ทณ่ฟ‡ๆญŒๆ›ฒใ€‚", - "backVote": "{0} ๆŠ•็ฅจๆ’ญๆ”พไธŠไธ€้ฆ–ๆญŒใ€‚[{1}/{2}]", - "backed": "`{0}` ๅทฒๅˆ‡ๆขๅˆฐไธŠไธ€้ฆ–ๆญŒๆ›ฒใ€‚", - "leaveVote": "{0} ๆŠ•็ฅจๅœๆญขๆ’ญๆ”พใ€‚[{1}/{2}]", - "left": "`{0}` ๅทฒๅœๆญขๆ’ญๆ”พๅ™จใ€‚", - "seek": "ๅทฒๅฐ†ๆ’ญๆ”พๅ™จๅฎšไฝๅˆฐ **{0}**", - "repeat": "ๅพช็Žฏๆจกๅผๅทฒ่ฎพ็ฝฎไธบ `{0}`", - "cleared": "ๅทฒๆธ…็ฉบ `{0}` ไธญ็š„ๆ‰€ๆœ‰ๆ›ฒ็›ฎ", - "removed": "ๅทฒไปŽ้˜Ÿๅˆ—ไธญ็งป้™ค `{0}` ้ฆ–ๆญŒใ€‚", - "forward": "ๅทฒๅฟซ่ฟ›ๅˆฐ **{0}**", - "rewind": "ๅทฒๅฟซ้€€ๅˆฐ **{0}**", - "replay": "ๆญฃๅœจ้‡ๆ’ญๅฝ“ๅ‰ๆญŒๆ›ฒใ€‚", - "swapped": "`{0}` ๅ’Œ `{1}` ๅทฒไบคๆขไฝ็ฝฎ", - "moved": "ๅทฒๅฐ† `{0}` ็งปๅŠจๅˆฐ `{1}`", - "autoplay": "่‡ชๅŠจๆ’ญๆ”พๆจกๅผ็Žฐๅทฒ **{0}**", - "notdj": "ๆ‚จไธๆ˜ฏ DJใ€‚ๅฝ“ๅ‰ DJ ๆ˜ฏ {0}ใ€‚", - "djToMe": "ๆ‚จไธ่ƒฝๅฐ† DJ ่บซไปฝ่ฝฌ็งป็ป™่‡ชๅทฑๆˆ–ๆœบๅ™จไบบใ€‚", - "djNotInChannel": "`{0}` ไธๅœจ่ฏญ้Ÿณ้ข‘้“ไธญใ€‚", - "djswap": "ๆ‚จๅทฒๅฐ† DJ ่บซไปฝ่ฝฌ็งป็ป™ `{0}`ใ€‚", - "voicelinkQueueFull": "ๆŠฑๆญ‰๏ผŒ้˜Ÿๅˆ—ๅทฒ่พพๅˆฐๆœ€ๅคง `{0}` ้ฆ–ๆญŒ๏ผ", - "voicelinkOutofList": "่ฏท่พ“ๅ…ฅๆœ‰ๆ•ˆ็š„ๆ›ฒ็›ฎๅบๅท๏ผ", - "voicelinkDuplicateTrack": "ๆŠฑๆญ‰๏ผŒ่ฏฅๆ›ฒ็›ฎๅทฒๅœจ้˜Ÿๅˆ—ไธญใ€‚", - "decodeError": "่งฃ็ ๆ–‡ไปถๆ—ถๅ‡บ้”™๏ผ", - "invalidStartTime": "ๆ— ๆ•ˆ็š„ๅผ€ๅง‹ๆ—ถ้—ด๏ผŒๅฟ…้กปๅœจ `00:00` ๅ’Œ `{0}` ไน‹้—ด", - "invalidEndTime": "ๆ— ๆ•ˆ็š„็ป“ๆŸๆ—ถ้—ด๏ผŒๅฟ…้กปๅœจ `00:00` ๅ’Œ `{0}` ไน‹้—ด", - "invalidTimeOrder": "็ป“ๆŸๆ—ถ้—ดไธ่ƒฝๅฐไบŽๆˆ–็ญ‰ไบŽๅผ€ๅง‹ๆ—ถ้—ด", - "setStageAnnounceTemplate": "ๅฎŒๆˆ๏ผไปŽ็Žฐๅœจ่ตท๏ผŒๆ‚จๆ‰€ๅœจ็š„่ฏญ้Ÿณ็Šถๆ€ๅฐ†ๆŒ‰็…งๆจกๆฟๅ‘ฝๅใ€‚ๅ‡ ็ง’ๅŽๅณๅฏ็œ‹ๅˆฐๆ›ดๆ–ฐใ€‚", - "createSongRequestChannel": "ๅทฒๅˆ›ๅปบ็‚นๆญŒ้ข‘้“ ({0})๏ผๆ‚จๅฏไปฅๅœจ่ฏฅ้ข‘้“่พ“ๅ…ฅๆญŒๆ›ฒๅ็งฐๆˆ–้“พๆŽฅๆฅ็‚นๆญŒ๏ผŒๆ— ้œ€ไฝฟ็”จๆœบๅ™จไบบๅ‰็ผ€ใ€‚" -} \ No newline at end of file + "common": { + "errors": { + "unknown": "โš ๏ธ ๆ‰ง่กŒๅ‘ฝไปคๆ—ถๅ‡บ้”™๏ผ่ฏท็จๅŽๅ†่ฏ•๏ผŒๆˆ–ๅŠ ๅ…ฅๆˆ‘ไปฌ็š„ Discord ๆœๅŠกๅ™จ่Žทๅ–ๆ”ฏๆŒใ€‚", + "missingIntents": "ๆŠฑๆญ‰๏ผŒๆ— ๆณ•ๆ‰ง่กŒๆญคๅ‘ฝไปค๏ผŒๅ› ไธบ็ผบๅฐ‘ๆ‰€้œ€็š„่ฏทๆฑ‚ intent๏ผš`({0})`ใ€‚" + }, + "status": { + "enabled": "ๅทฒๅฏ็”จ", + "disabled": "ๅทฒ็ฆ็”จ", + "live": "็›ดๆ’ญ" + } + }, + "player": { + "errors": { + "nodeReconnect": "่ฏทๅœจ่Š‚็‚น้‡่ฟžๅŽๅ†่ฏ•๏ผ", + "noPlaySource": "ๆ‰พไธๅˆฐๅฏๆ’ญๆ”พ็š„่ต„ๆบ๏ผ", + "noPlayer": "่ฏฅๆœๅŠกๅ™จไธŠๆฒกๆœ‰ๆ‰พๅˆฐๆ’ญๆ”พๅ™จใ€‚", + "noTrackPlaying": "ๅฝ“ๅ‰ๆฒกๆœ‰ๆญฃๅœจๆ’ญๆ”พ็š„ๆญŒๆ›ฒ", + "noTrackFound": "ๆœชๆ‰พๅˆฐๅฏนๅบ”็š„ๆญŒๆ›ฒ๏ผŒ่ฏทๆไพ›ๆœ‰ๆ•ˆ้“พๆŽฅใ€‚" + }, + "dropdown": { + "trackSelect": "้€‰ๆ‹ฉ่ฆ่ทณ่ฝฌ็š„ๆญŒๆ›ฒ ...", + "filterSelect": "้€‰ๆ‹ฉ่ฆๅบ”็”จ็š„้Ÿณๆ•ˆ ..." + }, + "buttons": { + "back": "ไธŠไธ€้ฆ–", + "pause": "ๆš‚ๅœ", + "resume": "็ปง็ปญ", + "skip": "ไธ‹ไธ€้ฆ–", + "leave": "้€€ๅ‡บ", + "loop": "ๅพช็Žฏ", + "volumeUp": "้Ÿณ้‡+", + "volumeDown": "้Ÿณ้‡-", + "volumeMute": "้™้Ÿณ", + "volumeUnmute": "ๅ–ๆถˆ้™้Ÿณ", + "autoPlay": "่‡ชๅŠจๆ’ญๆ”พ", + "shuffle": "้šๆœบๆ’ญๆ”พ", + "forward": "ๅฟซ่ฟ›", + "rewind": "ๅฟซ้€€", + "lyrics": "ๆญŒ่ฏ" + }, + "playback": { + "nowplayingDesc": "**ๆญฃๅœจๆ’ญๆ”พ:**\n```{0}```", + "nowplayingField": "ๅณๅฐ†ๆ’ญๆ”พ:", + "nowplayingLink": "ๅœจ {0} ๆ”ถๅฌ", + "playlistLoad": " ๐ŸŽถ ๅทฒๅฐ†ๆ’ญๆ”พๅˆ—่กจ **{0}** ็š„ `{1}` ้ฆ–ๆญŒๆทปๅŠ ๅˆฐ้˜Ÿๅˆ—ไธญใ€‚", + "trackLoad": "ๅทฒๆทปๅŠ  **[{0}](<{1}>)** - **{2}** (`{3}`) ๅนถๅผ€ๅง‹ๆ’ญๆ”พใ€‚\n", + "trackLoadPos": "ๅทฒๆทปๅŠ  **[{0}](<{1}>)** - **{2}** (`{3}`) ๅˆฐ้˜Ÿๅˆ—ไฝ็ฝฎ **{4}**\n" + }, + "controls": { + "pause": { + "error": "ๆ’ญๆ”พๅ™จๅทฒๆš‚ๅœใ€‚", + "vote": "{0} ๆŠ•็ฅจๆš‚ๅœไบ†ๆญŒๆ›ฒใ€‚[{1}/{2}]", + "success": "`{0}` ๅทฒๆš‚ๅœๆ’ญๆ”พๅ™จใ€‚" + }, + "resume": { + "error": "ๆ’ญๆ”พๅ™จๆœชๆš‚ๅœใ€‚", + "vote": "{0} ๆŠ•็ฅจ็ปง็ปญๆ’ญๆ”พใ€‚[{1}/{2}]", + "success": "`{0}` ๅทฒ็ปง็ปญๆ’ญๆ”พใ€‚" + }, + "shuffle": { + "error": "่ฏทๅœจ้˜Ÿๅˆ—ไธญๆทปๅŠ ๆ›ดๅคšๆญŒๆ›ฒๅŽๅ†้šๆœบๆ’ญๆ”พใ€‚", + "vote": "{0} ๆŠ•็ฅจ้šๆœบๆ’ญๆ”พ้˜Ÿๅˆ—ใ€‚[{1}/{2}]", + "success": "้˜Ÿๅˆ—ๅทฒ้šๆœบๆŽ’ๅบใ€‚" + }, + "skip": { + "error": "ๆฒกๆœ‰ๆญŒๆ›ฒๅฏ่ทณ่ฟ‡ใ€‚", + "vote": "{0} ๆŠ•็ฅจ่ทณ่ฟ‡ๆญŒๆ›ฒใ€‚[{1}/{2}]", + "success": "`{0}` ๅทฒ่ทณ่ฟ‡ๆญŒๆ›ฒใ€‚" + }, + "back": { + "vote": "{0} ๆŠ•็ฅจๆ’ญๆ”พไธŠไธ€้ฆ–ๆญŒใ€‚[{1}/{2}]", + "success": "`{0}` ๅทฒๅˆ‡ๆขๅˆฐไธŠไธ€้ฆ–ๆญŒๆ›ฒใ€‚" + }, + "leave": { + "vote": "{0} ๆŠ•็ฅจๅœๆญขๆ’ญๆ”พใ€‚[{1}/{2}]", + "success": "`{0}` ๅทฒๅœๆญขๆ’ญๆ”พๅ™จใ€‚" + }, + "seek": "ๅทฒๅฐ†ๆ’ญๆ”พๅ™จๅฎšไฝๅˆฐ **{0}**", + "repeat": "ๅพช็Žฏๆจกๅผๅทฒ่ฎพ็ฝฎไธบ `{0}`", + "forward": "ๅทฒๅฟซ่ฟ›ๅˆฐ **{0}**", + "rewind": "ๅทฒๅฟซ้€€ๅˆฐ **{0}**", + "replay": "ๆญฃๅœจ้‡ๆ’ญๅฝ“ๅ‰ๆญŒๆ›ฒใ€‚", + "autoplay": "่‡ชๅŠจๆ’ญๆ”พๆจกๅผ็Žฐๅทฒ **{0}**" + } + }, + "voice": { + "connection": { + "noChannel": "ๆœชๆฃ€ๆต‹ๅˆฐ่ฏญ้Ÿณ้ข‘้“๏ผŒ่ฏทๆไพ›ไธ€ไธชๆˆ–ๅŠ ๅ…ฅ่ฏญ้Ÿณ้ข‘้“ใ€‚", + "alreadyConnected": "ๅทฒ่ฟžๆŽฅๅˆฐ่ฏญ้Ÿณ้ข‘้“ใ€‚", + "noPermission": "ๆŠฑๆญ‰๏ผๆˆ‘ๆฒกๆœ‰ๆƒ้™ๅŠ ๅ…ฅๆˆ–ๅœจๆ‚จ็š„่ฏญ้Ÿณ้ข‘้“ไธญๅ‘่จ€ใ€‚", + "notInChannel": "{0}๏ผŒๆ‚จๅฟ…้กปๅœจ {1} ๆ‰่ƒฝไฝฟ็”จ่ฏญ้Ÿณๅ‘ฝไปคใ€‚ๅฆ‚ๆžœๆ‚จๅทฒๅœจ้ข‘้“๏ผŒ่ฏท้‡ๆ–ฐๅŠ ๅ…ฅ๏ผ", + "connect": "ๅทฒ่ฟžๆŽฅๅˆฐ {0}" + }, + "stageChannel": { + "setAnnounceTemplate": "ๅฎŒๆˆ๏ผไปŽ็Žฐๅœจ่ตท๏ผŒๆ‚จๆ‰€ๅœจ็š„่ฏญ้Ÿณ็Šถๆ€ๅฐ†ๆŒ‰็…งๆจกๆฟๅ‘ฝๅใ€‚ๅ‡ ็ง’ๅŽๅณๅฏ็œ‹ๅˆฐๆ›ดๆ–ฐใ€‚" + } + }, + "permissions": { + "noCreatePermission": "ๆŠฑๆญ‰๏ผๆˆ‘ๆฒกๆœ‰ๆƒ้™ๅˆ›ๅปบ็‚นๆญŒ้ข‘้“ใ€‚", + "missingPosition": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ‘˜ๆ‰่ƒฝๆ›ดๆ”นไฝ็ฝฎใ€‚", + "missingMode": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ‘˜ๆ‰่ƒฝๅˆ‡ๆขๅพช็Žฏๆจกๅผใ€‚", + "missingQueue": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ‘˜ๆ‰่ƒฝไปŽ้˜Ÿๅˆ—ไธญ็งป้™คๆ›ฒ็›ฎใ€‚", + "missingAutoPlay": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ‘˜ๆ‰่ƒฝๅฏ็”จๆˆ–็ฆ็”จ่‡ชๅŠจๆ’ญๆ”พ๏ผ", + "missingFunction": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ‘˜ๆ‰่ƒฝไฝฟ็”จๆญคๅŠŸ่ƒฝใ€‚", + "notDj": "ๆ‚จไธๆ˜ฏ DJใ€‚ๅฝ“ๅ‰ DJ ๆ˜ฏ {0}ใ€‚", + "djToSelf": "ๆ‚จไธ่ƒฝๅฐ† DJ ่บซไปฝ่ฝฌ็งป็ป™่‡ชๅทฑๆˆ–ๆœบๅ™จไบบใ€‚", + "djNotInChannel": "`{0}` ไธๅœจ่ฏญ้Ÿณ้ข‘้“ไธญใ€‚", + "djSwapped": "ๆ‚จๅทฒๅฐ† DJ ่บซไปฝ่ฝฌ็งป็ป™ `{0}`ใ€‚" + }, + "settings": { + "actions": { + "languageNotFound": "ๆœชๆ‰พๅˆฐ่ฏญ่จ€ๅŒ…๏ผ่ฏท้€‰ๆ‹ฉไธ€ไธชๅทฒๆœ‰็š„่ฏญ่จ€ๅŒ…ใ€‚", + "languageChanged": "ๅทฒๆˆๅŠŸๅˆ‡ๆขไธบ `{0}` ่ฏญ่จ€ๅŒ…ใ€‚", + "prefixSet": "ๅฎŒๆˆ๏ผๆˆ‘ๅœจๆ‚จๆœๅŠกๅ™จ็š„ๅ‰็ผ€็Žฐๅœจๆ˜ฏ `{0}`ใ€‚ๅฐ่ฏ•่ฟ่กŒ `{1}ping` ๆต‹่ฏ•ไธ€ไธ‹ๅงใ€‚", + "djSet": "ๅทฒๅฐ† DJ ่ฎพ็ฝฎไธบ {0}ใ€‚", + "queueModeSet": "ๅทฒๅฐ†้˜Ÿๅˆ—ๆจกๅผ่ฎพ็ฝฎไธบ `{0}`ใ€‚", + "mode247": "24/7 ๆจกๅผ็Žฐๅทฒ `{0}`ใ€‚", + "bypassVote": "ๆ‚จๆ‹ฅๆœ‰ `{0}` ๆŠ•็ฅจ็ณป็ปŸใ€‚", + "volumeSet": "ๅทฒๅฐ†้Ÿณ้‡่ฎพ็ฝฎไธบ `{0}`%", + "controllerToggled": "ๆ‚จ็Žฐๅœจๅทฒ `{0}` ้ŸณไนๆŽงๅˆถๅ™จใ€‚", + "duplicateTrackToggled": "ๆ‚จๅทฒ `{0}` ้‡ๅคๆ›ฒ็›ฎ้ข„้˜ฒๅŠŸ่ƒฝใ€‚", + "controllerMsgToggled": "ๆ‚จ็Žฐๅœจๅทฒ `{0}` ๆฅ่‡ช้ŸณไนๆŽงๅˆถๅ™จ็š„ๆถˆๆฏใ€‚", + "silentMsgToggled": "ๆ‚จๅทฒ {0} ้™้ป˜ๆถˆๆฏใ€‚" + }, + "menu": "ๆœๅŠกๅ™จ่ฎพ็ฝฎ | {0}", + "basic": { + "title": "โค๏ธ ๅŸบๆœฌไฟกๆฏ:", + "value": "```ๅ‰็ผ€: {0}\n่ฏญ่จ€: {1}\n้ŸณไนๆŽงๅˆถๅ™จ: {2}\nDJ ่ง’่‰ฒ: @{3}\nๆŠ•็ฅจ่ทณ่ฟ‡: {4}\n24/7: {5}\n้ป˜่ฎค้Ÿณ้‡: {6}%\nๆ’ญๆ”พๆ—ถ้•ฟ: {7}```" + }, + "queue": { + "title": "๐Ÿ”— ้˜Ÿๅˆ—ไฟกๆฏ:", + "value": "```้˜Ÿๅˆ—ๆจกๅผ: {0}\nๆœ€ๅคงๆญŒๆ›ฒๆ•ฐ: {1}\n้‡ๅคๆ›ฒ็›ฎ: {2}```" + }, + "voice": { + "title": "๐ŸŽค ่ฏญ้Ÿณ็Šถๆ€ไฟกๆฏ:" + }, + "permissions": { + "title": "โœจ ๆƒ้™:", + "value": "```{0} ็ฎก็†ๅ‘˜\n{1} ็ฎก็†ๆœๅŠกๅ™จ\n{2} ็ฎก็†้ข‘้“\n{3} ็ฎก็†ๆถˆๆฏ```" + } + }, + "ping": { + "title1": "ๆœบๅ™จไบบไฟกๆฏ:", + "title2": "ๆ’ญๆ”พๅ™จไฟกๆฏ:", + "field1": "```ๅˆ†็‰‡ ID: {0}/{1}\nๅˆ†็‰‡ๅปถ่ฟŸ: {2:.3f}s {3}\nๅŒบๅŸŸ: {4}```", + "field2": "```่Š‚็‚น: {0} - {1:.3f}s\nๆ’ญๆ”พๅ™จๆ•ฐ: {2}\n่ฏญ้ŸณๅŒบๅŸŸ: {3}```" + }, + "effects": { + "added": "ๅทฒๅบ”็”จ `{0}` ้Ÿณๆ•ˆใ€‚", + "cleared": "้Ÿณๆ•ˆๅทฒๆธ…้™ค๏ผ", + "tagInUse": "่ฏฅ้Ÿณๆ•ˆๅทฒๅœจไฝฟ็”จไธญ๏ผ่ฏทไฝฟ็”จ /cleareffect <ๆ ‡็ญพ> ็งป้™คใ€‚" + }, + "playlist": { + "view": { + "title": "{0} ็š„ๆ‰€ๆœ‰ๆ’ญๆ”พๅˆ—่กจ", + "headers": "ID:,ๆ—ถ้•ฟ:,ๅ็งฐ:,ๆ›ฒ็›ฎๆ•ฐ:", + "footer": "่พ“ๅ…ฅ /playlist play [ๆ’ญๆ”พๅˆ—่กจ] ๅฐ†ๅ…ถๆทปๅŠ ๅˆฐ้˜Ÿๅˆ—ไธญใ€‚", + "detailTitle": "ๆ’ญๆ”พๅˆ—่กจๆŸฅ็œ‹ๅ™จ", + "detailDesc": "```ๅ็งฐ | ID: {0} | {1}\nๆ€ปๆ›ฒ็›ฎ: {2}\nๆ‹ฅๆœ‰่€…: {3}\n็ฑปๅž‹: {4}\n```", + "permsValue": "๐Ÿ“– ่ฏปๅ–: โœ“ โœ๐Ÿฝ ๅ†™ๅ…ฅ: {0} ๐Ÿ—‘๏ธ ๅˆ ้™ค: {1}", + "permsValue2": "๐Ÿ“– ่ฏปๅ–: {0}", + "trackList": "ๆ›ฒ็›ฎๅˆ—่กจ", + "footer2": "ๆ€ปๆŒ็ปญๆ—ถ้—ด๏ผš{0}", + "playlistSelect": "้€‰ๆ‹ฉ่ฆๆŸฅ็œ‹็š„ๆ’ญๆ”พๅˆ—่กจ ..." + }, + "errors": { + "notFound": "ๆœชๆ‰พๅˆฐๆ’ญๆ”พๅˆ—่กจ [`{0}`]ใ€‚่พ“ๅ…ฅ /playlist view ๆŸฅ็œ‹ๆ‰€ๆœ‰ๆ’ญๆ”พๅˆ—่กจใ€‚", + "noAccess": "ๆŠฑๆญ‰๏ผๆ‚จๆ— ๆƒ่ฎฟ้—ฎๆญคๆ’ญๆ”พๅˆ—่กจ๏ผ", + "noTrack": "ๆŠฑๆญ‰๏ผๆ’ญๆ”พๅˆ—่กจ [`{0}`] ไธญๆฒกๆœ‰ๆ›ฒ็›ฎใ€‚", + "notAllowed": "่ฏฅๅ‘ฝไปคไธๆ”ฏๆŒๅทฒๅ…ณ่”ๆˆ–ๅ…ฑไบซ็š„ๆ’ญๆ”พๅˆ—่กจใ€‚", + "nameOverLimit": "ๆŠฑๆญ‰๏ผๆ’ญๆ”พๅˆ—่กจๅ็งฐไธ่ƒฝ่ถ…่ฟ‡ 10 ไธชๅญ—็ฌฆใ€‚", + "sameName": "ๆŠฑๆญ‰๏ผ่ฏทไธบๆ’ญๆ”พๅˆ—่กจ้€‰ๆ‹ฉไธ€ไธชๆ–ฐๅๅญ—ใ€‚", + "deleteDefault": "ไธๅ…่ฎธๅˆ ้™ค้ป˜่ฎคๆ’ญๆ”พๅˆ—่กจใ€‚", + "limitReached": "ๆ‚จๆœ€ๅคšๅช่ƒฝๅˆ›ๅปบ `{0}` ไธชๆ’ญๆ”พๅˆ—่กจ๏ผ", + "exists": "ๆ’ญๆ”พๅˆ—่กจ [`{0}`] ๅทฒๅญ˜ๅœจใ€‚", + "invalidUrl": "่ฏท่พ“ๅ…ฅๆœ‰ๆ•ˆ็š„ๅ…ฌๅ…ฑ Spotify ๆˆ– YouTube ๆ’ญๆ”พๅˆ—่กจ้“พๆŽฅใ€‚", + "trackLimitReached": "ๅทฒ่พพๅˆฐไธŠ้™๏ผๆฏไธชๆ’ญๆ”พๅˆ—่กจๆœ€ๅคšๅช่ƒฝๆทปๅŠ  `{0}` ้ฆ–ๆญŒใ€‚", + "playlistLinkNotAllowed": "ไธๅ…่ฎธไฝฟ็”จๆ’ญๆ”พๅˆ—่กจ้“พๆŽฅใ€‚", + "streamNotAllowed": "ไธๅ…่ฎธๅฐ†ๆตๅช’ไฝ“่ง†้ข‘ๆทปๅŠ ๅˆฐๆ’ญๆ”พๅˆ—่กจใ€‚", + "positionNotFound": "ๅœจๆ’ญๆ”พๅˆ—่กจ [`{1}`] ไธญๆ‰พไธๅˆฐไฝ็ฝฎ `{0}`๏ผ", + "streaming": "ไธๅ…่ฎธๅฐ†ๆตๅช’ไฝ“่ง†้ข‘ๆทปๅŠ ๅˆฐๆ’ญๆ”พๅˆ—่กจ๏ผ", + "track": "ๆทปๅŠ ๆ›ฒ็›ฎๅˆฐๆ’ญๆ”พๅˆ—่กจๆ—ถๅ‡บ็Žฐ้—ฎ้ข˜๏ผ", + "trackLimited": "ๅทฒ่พพๅˆฐไธŠ้™๏ผๆ‚จๆœ€ๅคšๅช่ƒฝๅœจๆ’ญๆ”พๅˆ—่กจไธญๆทปๅŠ  {0} ้ฆ–ๆญŒใ€‚", + "trackRepeated": "่ฏฅๆ›ฒ็›ฎๅทฒๅœจๆ’ญๆ”พๅˆ—่กจไธญ๏ผ" + }, + "actions": { + "play": "ๅทฒๅฐ†ๆ’ญๆ”พๅˆ—่กจ [`{0}`] ็š„ `{1}` ้ฆ–ๆญŒๆทปๅŠ ๅˆฐ้˜Ÿๅˆ—ไธญใ€‚", + "removed": "ๆ‚จๅทฒๅˆ ้™คๆ’ญๆ”พๅˆ—่กจ [`{0}`]ใ€‚", + "created": "ๆ‚จๅทฒๅˆ›ๅปบ `{0}` ๆ’ญๆ”พๅˆ—่กจใ€‚่พ“ๅ…ฅ /playlist view ๆŸฅ็œ‹่ฏฆๆƒ…ใ€‚", + "renamed": "ๆ‚จๅทฒๅฐ† `{0}` ้‡ๅ‘ฝๅไธบ `{1}`ใ€‚", + "trackRemoved": "๐Ÿ‘‹ ๅทฒไปŽ {1} ็š„ๆ’ญๆ”พๅˆ—่กจ [`{2}`] ไธญ็งป้™ค **{0}**ใ€‚", + "cleared": "ๆ‚จๅทฒๆˆๅŠŸๆธ…็ฉบๆ’ญๆ”พๅˆ—่กจ [`{0}`]ใ€‚", + "trackAdded": "โค๏ธ ๅทฒๅฐ† **{0}** ๆทปๅŠ ๅˆฐ {1} ็š„ๆ’ญๆ”พๅˆ—่กจ [`{2}`]๏ผ" + }, + "sharing": { + "sendErrorPlayer": "ๆŠฑๆญ‰๏ผๆ‚จไธ่ƒฝ็ป™่‡ชๅทฑๅ‘้€้‚€่ฏทใ€‚", + "sendErrorBot": "ๆŠฑๆญ‰๏ผๆ‚จไธ่ƒฝ็ป™ๆœบๅ™จไบบๅ‘้€้‚€่ฏทใ€‚", + "belongs": "ๆŠฑๆญ‰๏ผๆญคๆ’ญๆ”พๅˆ—่กจๅฑžไบŽ <@{0}>ใ€‚", + "alreadyShared": "ๆŠฑๆญ‰๏ผๆญคๆ’ญๆ”พๅˆ—่กจๅทฒไธŽ {0} ๅ…ฑไบซใ€‚", + "alreadySent": "ๆŠฑๆญ‰๏ผๆ‚จไน‹ๅ‰ๅทฒ็ป็ป™่ฏฅ็”จๆˆทๅ‘้€่ฟ‡้‚€่ฏทใ€‚", + "noAccount": "{0} ่ฟ˜ๆฒกๆœ‰ๅˆ›ๅปบๆ’ญๆ”พๅˆ—่กจ่ดฆๅทใ€‚", + "invitationSent": "้‚€่ฏทๅทฒๅ‘้€็ป™ {0}ใ€‚" + }, + "inbox": { + "full": "ๆŠฑๆญ‰๏ผ{0} ็š„ๆ”ถไปถ็ฎฑๅทฒๆปกใ€‚", + "noMessages": "ๆ‚จ็š„ๆ”ถไปถ็ฎฑไธญๆฒกๆœ‰ๆถˆๆฏใ€‚" + } + }, + "search": { + "noLinkSupport": "ๆœ็ดขๅ‘ฝไปคไธๆ”ฏๆŒ้“พๆŽฅ๏ผ", + "title": "ๆœ็ดขๅ…ณ้”ฎ่ฏ: {0}", + "desc": "โžฅ ๅนณๅฐ: {0} **{1}**\nโžฅ ็ป“ๆžœๆ•ฐ: **{2}**\n\n{3}", + "wait": "่ฏท้€‰ๆ‹ฉ่ฆๆทปๅŠ ๅˆฐ้˜Ÿๅˆ—็š„ๆญŒๆ›ฒใ€‚", + "success": "ๆญŒๆ›ฒๅทฒๆทปๅŠ ๅˆฐ้˜Ÿๅˆ—ใ€‚" + }, + "voting": { + "voted": "ๆ‚จๅทฒๆŠ•็ฅจ๏ผ" + }, + "time": { + "formatError": "ๆ—ถ้—ดๆ ผๅผ้”™่ฏฏใ€‚็คบไพ‹: 2:42 ๆˆ– 12:39:31", + "invalidStartTime": "ๆ— ๆ•ˆ็š„ๅผ€ๅง‹ๆ—ถ้—ด๏ผŒๅฟ…้กปๅœจ `00:00` ๅ’Œ `{0}` ไน‹้—ด", + "invalidEndTime": "ๆ— ๆ•ˆ็š„็ป“ๆŸๆ—ถ้—ด๏ผŒๅฟ…้กปๅœจ `00:00` ๅ’Œ `{0}` ไน‹้—ด" + }, + "lyrics": { + "notFound": "ๆœชๆ‰พๅˆฐๆญŒ่ฏใ€‚่พ“ๅ…ฅ /lyrics <ๆญŒๆ›ฒๅ> <ไฝœ่€…> ๆŸฅๆ‰พๆญŒ่ฏใ€‚" + }, + "queue": { + "management": { + "title": "ๅณๅฐ†ๆ’ญๆ”พ้˜Ÿๅˆ—:", + "cleared": "ๅทฒๆธ…็ฉบ `{0}` ไธญ็š„ๆ‰€ๆœ‰ๆ›ฒ็›ฎ", + "removed": "ๅทฒไปŽ้˜Ÿๅˆ—ไธญ็งป้™ค `{0}` ้ฆ–ๆญŒใ€‚", + "swapped": "`{0}` ๅ’Œ `{1}` ๅทฒไบคๆขไฝ็ฝฎ", + "moved": "ๅทฒๅฐ† `{0}` ็งปๅŠจๅˆฐ `{1}`" + }, + "view": { + "historyTitle": "ๆ’ญๆ”พๅކๅฒ:", + "title": "้Ÿณไน้˜Ÿๅˆ—", + "desc": "**ๆญฃๅœจๆ’ญๆ”พ: [็‚นๆˆ‘]({0}) โฎฏ**\n{1}" + }, + "errors": { + "queueFull": "ๆŠฑๆญ‰๏ผŒ้˜Ÿๅˆ—ๅทฒ่พพๅˆฐๆœ€ๅคง `{0}` ้ฆ–ๆญŒ๏ผ", + "outOfList": "่ฏท่พ“ๅ…ฅๆœ‰ๆ•ˆ็š„ๆ›ฒ็›ฎๅบๅท๏ผ", + "duplicateTrack": "ๆŠฑๆญ‰๏ผŒ่ฏฅๆ›ฒ็›ฎๅทฒๅœจ้˜Ÿๅˆ—ไธญใ€‚" + } + }, + "songRequest": { + "channelCreated": "ๅทฒๅˆ›ๅปบ็‚นๆญŒ้ข‘้“ ({0})๏ผๆ‚จๅฏไปฅๅœจ่ฏฅ้ข‘้“่พ“ๅ…ฅๆญŒๆ›ฒๅ็งฐๆˆ–้“พๆŽฅๆฅ็‚นๆญŒ๏ผŒๆ— ้œ€ไฝฟ็”จๆœบๅ™จไบบๅ‰็ผ€ใ€‚" + }, + "pagination": { + "prev": "ไธŠไธ€้กต", + "next": "ไธ‹ไธ€้กต", + "play": "ๆ’ญๆ”พ", + "share": "ๅˆ†ไบซ", + "export": "ๅฏผๅ‡บ", + "delete": "ๅˆ ้™ค", + "page": { + "title": "่พ“ๅ…ฅ้กต็ ", + "field": "้กต็ " + } + } +} diff --git a/langs/ZHTW.json b/langs/ZHTW.json index 7c5de56..1b9d148 100644 --- a/langs/ZHTW.json +++ b/langs/ZHTW.json @@ -1,172 +1,262 @@ { - "unknownException": "โš ๏ธ ๅŸท่กŒๆŒ‡ไปคๆ™‚็™ผ็”Ÿ้Œฏ่ชค๏ผ่ซ‹็จๅพŒๅ†่ฉฆ๏ผŒๆˆ–ๅŠ ๅ…ฅๆˆ‘ๅ€‘็š„ Discord ไผบๆœๅ™จๅ–ๅพ—ๅ”ๅŠฉใ€‚", - "enabled": "ๅทฒๅ•Ÿ็”จ", - "disabled": "ๅทฒๅœ็”จ", - "nodeReconnect": "่ซ‹ๅœจ็ฏ€้ปž้‡ๆ–ฐ้€ฃ็ทšๅพŒๅ†่ฉฆ๏ผ", - "noChannel": "ๆœชๅตๆธฌๅˆฐ่ชž้Ÿณ้ ป้“๏ผŒ่ซ‹ๆไพ›ไธ€ๅ€‹ๆˆ–ๅ…ˆๅŠ ๅ…ฅ่ชž้Ÿณ้ ป้“ใ€‚", - "alreadyConnected": "ๅทฒ้€ฃ็ทš่‡ณ่ชž้Ÿณ้ ป้“ใ€‚", - "noPermission": "ๆŠฑๆญ‰๏ผๆˆ‘ๆฒ’ๆœ‰ๆฌŠ้™ๅŠ ๅ…ฅๆˆ–ๅœจๆ‚จ็š„่ชž้Ÿณ้ ป้“ไธญ็™ผ่จ€ใ€‚", - "noCreatePermission": "ๆŠฑๆญ‰๏ผๆˆ‘ๆฒ’ๆœ‰ๆฌŠ้™ๅปบ็ซ‹้ปžๆญŒ้ ป้“ใ€‚", - "noPlaySource": "ๆ‰พไธๅˆฐๅฏๆ’ญๆ”พ็š„ไพ†ๆบ๏ผ", - "noPlayer": "ๆญคไผบๆœๅ™จไธŠๆฒ’ๆœ‰ๆ‰พๅˆฐๆ’ญๆ”พๅ™จใ€‚", - "notVote": "ๆญคๆŒ‡ไปค้œ€่ฆๆ‚จๅ…ˆๆŠ•็ฅจ๏ผ่ผธๅ…ฅ `/vote` ไบ†่งฃๆ›ดๅคšใ€‚", - "missingIntents": "ๆŠฑๆญ‰๏ผŒ็„กๆณ•ๅŸท่กŒๆญคๆŒ‡ไปค๏ผŒๅ› ็‚บ็ผบๅฐ‘ๅฟ…่ฆ็š„่ซ‹ๆฑ‚ intent๏ผš`({0})`ใ€‚", - "languageNotFound": "ๆœชๆ‰พๅˆฐ่ชž่จ€ๅŒ…๏ผ่ซ‹้ธๆ“‡ไธ€ๅ€‹็พๆœ‰็š„่ชž่จ€ๅŒ…ใ€‚", - "changedLanguage": "ๅทฒๆˆๅŠŸๅˆ‡ๆ›็‚บ `{0}` ่ชž่จ€ๅŒ…ใ€‚", - "setPrefix": "ๅฎŒๆˆ๏ผๆˆ‘ๅœจไผบๆœๅ™จ็š„ๅ‰็ถด็พๅœจๆ˜ฏ `{0}`ใ€‚่ฉฆ่‘—่ผธๅ…ฅ `{1}ping` ๆธฌ่ฉฆไธ€ไธ‹ๅงใ€‚", - "setDJ": "ๅทฒๅฐ‡ DJ ่จญๅฎš็‚บ {0}ใ€‚", - "setQueue": "ๅทฒๅฐ‡้šŠๅˆ—ๆจกๅผ่จญๅฎš็‚บ `{0}`ใ€‚", - "247": "24/7 ๆจกๅผ็พๅœจๆ˜ฏ `{0}`ใ€‚", - "bypassVote": "ๆ‚จๆ“ๆœ‰ `{0}` ๆŠ•็ฅจ็ณป็ตฑใ€‚", - "setVolume": "ๅทฒๅฐ‡้Ÿณ้‡่จญๅฎš็‚บ `{0}`%", - "toggleController": "ๆ‚จ็พๅœจๅทฒ `{0}` ้Ÿณๆจ‚ๆŽงๅˆถๅ™จใ€‚", - "toggleDuplicateTrack": "ๆ‚จๅทฒ `{0}` ้‡่ค‡ๆ›ฒ็›ฎ้˜ฒๆญขๅŠŸ่ƒฝใ€‚", - "toggleControllerMsg": "ๆ‚จ็พๅœจๅทฒ `{0}` ไพ†่‡ช้Ÿณๆจ‚ๆŽงๅˆถๅ™จ็š„่จŠๆฏใ€‚", - "toggleSilentMsg": "ๆ‚จๅทฒ {0} ้œ้Ÿณ่จŠๆฏใ€‚", - "settingsMenu": "ไผบๆœๅ™จ่จญๅฎš | {0}", - "settingsTitle": "โค๏ธ ๅŸบๆœฌ่ณ‡่จŠ:", - "settingsValue": "```ๅ‰็ถด: {0}\n่ชž่จ€: {1}\n้Ÿณๆจ‚ๆŽงๅˆถๅ™จ: {2}\nDJ ่บซๅˆ†็ต„: @{3}\nๆŠ•็ฅจ็•ฅ้Ž: {4}\n24/7: {5}\n้ ่จญ้Ÿณ้‡: {6}%\nๆ’ญๆ”พๆ™‚้•ท: {7}```", - "settingsTitle2": "๐Ÿ”— ้šŠๅˆ—่ณ‡่จŠ:", - "settingsValue2": "```้šŠๅˆ—ๆจกๅผ: {0}\nๆœ€ๅคงๆญŒๆ›ฒๆ•ธ: {1}\n้‡่ค‡ๆ›ฒ็›ฎ: {2}```", - "settingsTitle3": "๐ŸŽค ่ชž้Ÿณ็‹€ๆ…‹่ณ‡่จŠ:", - "settingsPermTitle": "โœจ ๆฌŠ้™:", - "settingsPermValue": "```{0} ็ฎก็†ๅ“ก\n{1} ็ฎก็†ไผบๆœๅ™จ\n{2} ็ฎก็†้ ป้“\n{3} ็ฎก็†่จŠๆฏ```", - "pingTitle1": "ๆฉŸๅ™จไบบ่ณ‡่จŠ:", - "pingTitle2": "ๆ’ญๆ”พๅ™จ่ณ‡่จŠ:", - "pingField1": "```ๅˆ†็‰‡ ID: {0}/{1}\nๅˆ†็‰‡ๅปถ้ฒ: {2:.3f}s {3}\nๅ€ๅŸŸ: {4}```", - "pingField2": "```็ฏ€้ปž: {0} - {1:.3f}s\nๆ’ญๆ”พๅ™จๆ•ธ: {2}\n่ชž้Ÿณๅ€ๅŸŸ: {3}```", - "addEffect": "ๅทฒๅฅ—็”จ `{0}` ๆ•ˆๆžœใ€‚", - "clearEffect": "้Ÿณๆ•ˆๅทฒๆธ…้™ค๏ผ", - "filterTagAlreadyInUse": "่ฉฒ้Ÿณๆ•ˆๅทฒๅœจไฝฟ็”จไธญ๏ผ่ซ‹ไฝฟ็”จ /cleareffect <ๆจ™็ฑค> ็งป้™คใ€‚", - "playlistViewTitle": "๐Ÿ“œ {0} ็š„ๆ‰€ๆœ‰ๆ’ญๆ”พๆธ…ๅ–ฎ", - "playlistViewHeaders": "ID:,ๆ™‚้•ท:,ๅ็จฑ:,ๆ›ฒ็›ฎๆ•ธ:", - "playlistFooter": "่ผธๅ…ฅ /playlist play [ๆ’ญๆ”พๆธ…ๅ–ฎ] ๅฐ‡ๅ…ถๅŠ ๅ…ฅ้šŠๅˆ—ใ€‚", - "playlistNotFound": "ๆœชๆ‰พๅˆฐๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`]ใ€‚่ผธๅ…ฅ /playlist view ๆŸฅ็œ‹ๆ‰€ๆœ‰ๆ’ญๆ”พๆธ…ๅ–ฎใ€‚", - "playlistNotAccess": "ๆŠฑๆญ‰๏ผๆ‚จ็„กๆณ•ๅญ˜ๅ–ๆญคๆ’ญๆ”พๆธ…ๅ–ฎ๏ผ", - "playlistNoTrack": "ๆŠฑๆญ‰๏ผๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`] ไธญๆฒ’ๆœ‰ๆ›ฒ็›ฎใ€‚", - "playlistNotAllow": "ๆญคๆŒ‡ไปคไธๆ”ฏๆดๅทฒ้€ฃ็ตๆˆ–ๅ…ฑไบซ็š„ๆ’ญๆ”พๆธ…ๅ–ฎใ€‚", - "playlistPlay": "ๅทฒๅฐ‡ๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`] ็š„ `{1}` ้ฆ–ๆญŒๆ›ฒๅŠ ๅ…ฅ้šŠๅˆ—ใ€‚", - "playlistOverText": "ๆŠฑๆญ‰๏ผๆ’ญๆ”พๆธ…ๅ–ฎๅ็จฑไธ่ƒฝ่ถ…้Ž 10 ๅ€‹ๅญ—ๅ…ƒใ€‚", - "playlistSameName": "ๆŠฑๆญ‰๏ผ่ซ‹็‚บๆ’ญๆ”พๆธ…ๅ–ฎ้ธๆ“‡ๆ–ฐๅ็จฑใ€‚", - "playlistDeleteError": "ไธๅ…่จฑๅˆช้™ค้ ่จญๆ’ญๆ”พๆธ…ๅ–ฎใ€‚", - "playlistRemove": "ๆ‚จๅทฒๅˆช้™คๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`]ใ€‚", - "playlistSendErrorPlayer": "ๆŠฑๆญ‰๏ผๆ‚จไธ่ƒฝ้‚€่ซ‹่‡ชๅทฑใ€‚", - "playlistSendErrorBot": "ๆŠฑๆญ‰๏ผๆ‚จไธ่ƒฝ้‚€่ซ‹ๆฉŸๅ™จไบบใ€‚", - "playlistBelongs": "ๆŠฑๆญ‰๏ผๆญคๆ’ญๆ”พๆธ…ๅ–ฎๅฑฌๆ–ผ <@{0}>ใ€‚", - "playlistShare": "ๆŠฑๆญ‰๏ผๆญคๆ’ญๆ”พๆธ…ๅ–ฎๅทฒ่ˆ‡ {0} ๅˆ†ไบซใ€‚", - "playlistSent": "ๆŠฑๆญ‰๏ผๆ‚จไน‹ๅ‰ๅทฒ็ถ“้‚€่ซ‹้Ž่ฉฒไฝฟ็”จ่€…ใ€‚", - "noPlaylistAcc": "{0} ๅฐšๆœชๅปบ็ซ‹ๆ’ญๆ”พๆธ…ๅ–ฎๅธณ่™Ÿใ€‚", - "overPlaylistCreation": "ๆ‚จๆœ€ๅคšๅช่ƒฝๅปบ็ซ‹ `{0}` ๅ€‹ๆ’ญๆ”พๆธ…ๅ–ฎ๏ผ", - "playlistExists": "ๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`] ๅทฒๅญ˜ๅœจใ€‚", - "playlistNotInvalidUrl": "่ซ‹่ผธๅ…ฅๆœ‰ๆ•ˆ็š„ๅ…ฌ้–‹ Spotify ๆˆ– YouTube ๆ’ญๆ”พๆธ…ๅ–ฎ้€ฃ็ตใ€‚", - "playlistCreated": "ๆ‚จๅทฒๅปบ็ซ‹ `{0}` ๆ’ญๆ”พๆธ…ๅ–ฎใ€‚่ผธๅ…ฅ /playlist view ๆŸฅ็œ‹่ฉณๆƒ…ใ€‚", - "playlistRenamed": "ๆ‚จๅทฒๅฐ‡ `{0}` ้‡ๆ–ฐๅ‘ฝๅ็‚บ `{1}`ใ€‚", - "playlistLimitTrack": "ๅทฒ้”ๅˆฐไธŠ้™๏ผๆฏๅ€‹ๆ’ญๆ”พๆธ…ๅ–ฎๆœ€ๅคšๅช่ƒฝๆ–ฐๅขž `{0}` ้ฆ–ๆญŒๆ›ฒใ€‚", - "playlistPlaylistLink": "ไธๅ…่จฑไฝฟ็”จๆ’ญๆ”พๆธ…ๅ–ฎ้€ฃ็ตใ€‚", - "playlistStream": "ไธๅ…่จฑๅฐ‡ไธฒๆตๅฝฑ็‰‡ๅŠ ๅ…ฅๆ’ญๆ”พๆธ…ๅ–ฎใ€‚", - "playlistPositionNotFound": "ๅœจๆ’ญๆ”พๆธ…ๅ–ฎ [`{1}`] ไธญๆ‰พไธๅˆฐไฝ็ฝฎ `{0}`๏ผ", - "playlistRemoved": "๐Ÿ‘‹ ๅทฒๅพž {1} ็š„ๆ’ญๆ”พๆธ…ๅ–ฎ [`{2}`] ไธญ็งป้™ค **{0}**ใ€‚", - "playlistClear": "ๆ‚จๅทฒๆˆๅŠŸๆธ…็ฉบๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`]ใ€‚", - "playlistView": "ๆ’ญๆ”พๆธ…ๅ–ฎๆชข่ฆ–ๅ™จ", - "playlistViewDesc": "```ๅ็จฑ | ID: {0} | {1}\n็ธฝๆ›ฒ็›ฎ: {2}\nๆ“ๆœ‰่€…: {3}\n้กžๅž‹: {4}\n```", - "playlistViewPermsValue": "๐Ÿ“– ่ฎ€ๅ–: โœ“ โœ๐Ÿฝ ๅฏซๅ…ฅ: {0} ๐Ÿ—‘๏ธ ็งป้™ค: {1}", - "playlistViewPermsValue2": "๐Ÿ“– ่ฎ€ๅ–: {0}", - "playlistViewTrack": "ๆ›ฒ็›ฎๅˆ—่กจ", - "playlistViewPage": "็ฌฌ {0}/{1} ้  | ็ธฝๆ™‚้•ท: {2}", - "inboxFull": "ๆŠฑๆญ‰๏ผ{0} ็š„ๆ”ถไปถๅŒฃๅทฒๆปฟใ€‚", - "inboxNoMsg": "ๆ‚จ็š„ๆ”ถไปถๅŒฃไธญๆฒ’ๆœ‰่จŠๆฏใ€‚", - "invitationSent": "้‚€่ซ‹ๅทฒ็™ผ้€็ตฆ {0}ใ€‚", - "notInChannel": "{0}๏ผŒๆ‚จๅฟ…้ ˆๅœจ {1} ๆ‰่ƒฝไฝฟ็”จ่ชž้ŸณๆŒ‡ไปคใ€‚ๅฆ‚ๆžœๆ‚จๅทฒๅœจ้ ป้“๏ผŒ่ซ‹้‡ๆ–ฐๅŠ ๅ…ฅ๏ผ", - "noTrackPlaying": "็›ฎๅ‰ๆฒ’ๆœ‰ๆญฃๅœจๆ’ญๆ”พ็š„ๆญŒๆ›ฒ", - "noTrackFound": "ๆœชๆ‰พๅˆฐๅฐๆ‡‰็š„ๆญŒๆ›ฒ๏ผŒ่ซ‹ๆไพ›ๆœ‰ๆ•ˆ้€ฃ็ตใ€‚", - "noLinkSupport": "ๆœๅฐ‹ๆŒ‡ไปคไธๆ”ฏๆด้€ฃ็ต๏ผ", - "voted": "ๆ‚จๅทฒๆŠ•็ฅจ๏ผ", - "missingPosPerm": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ“ก่ƒฝๆ›ดๆ”นไฝ็ฝฎใ€‚", - "missingModePerm": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ“ก่ƒฝๅˆ‡ๆ›ๅพช็’ฐๆจกๅผใ€‚", - "missingQueuePerm": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ“ก่ƒฝๅพž้šŠๅˆ—ไธญ็งป้™คๆ›ฒ็›ฎใ€‚", - "missingAutoPlayPerm": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ“ก่ƒฝๅ•Ÿ็”จๆˆ–ๅœ็”จ่‡ชๅ‹•ๆ’ญๆ”พ๏ผ", - "missingFunctionPerm": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ“ก่ƒฝไฝฟ็”จๆญคๅŠŸ่ƒฝใ€‚", - "timeFormatError": "ๆ™‚้–“ๆ ผๅผ้Œฏ่ชคใ€‚็ฏ„ไพ‹: 2:42 ๆˆ– 12:39:31", - "lyricsNotFound": "ๆœชๆ‰พๅˆฐๆญŒ่ฉžใ€‚่ผธๅ…ฅ /lyrics <ๆญŒๆ›ฒๅ็จฑ> <ไฝœ่€…> ๆŸฅๆ‰พๆญŒ่ฉžใ€‚", - "missingTrackInfo": "้ƒจๅˆ†ๆ›ฒ็›ฎ่ณ‡่จŠ็ผบๅคฑใ€‚", - "noVoiceChannel": "ๆœชๆ‰พๅˆฐ่ชž้Ÿณ้ ป้“๏ผ", - "playlistAddError": "ไธๅ…่จฑๅฐ‡ไธฒๆตๅฝฑ็‰‡ๅŠ ๅ…ฅๆ’ญๆ”พๆธ…ๅ–ฎ๏ผ", - "playlistAddError2": "ๆ–ฐๅขžๆ›ฒ็›ฎๅˆฐๆ’ญๆ”พๆธ…ๅ–ฎๆ™‚็™ผ็”Ÿๅ•้กŒ๏ผ", - "playlistLimited": "ๅทฒ้”ๅˆฐไธŠ้™๏ผๆ’ญๆ”พๆธ…ๅ–ฎๆœ€ๅคšๅช่ƒฝๆ–ฐๅขž {0} ้ฆ–ๆญŒๆ›ฒใ€‚", - "playlistRepeated": "ๆญคๆ›ฒ็›ฎๅทฒๅœจๆ’ญๆ”พๆธ…ๅ–ฎไธญ๏ผ", - "playlistAdded": "โค๏ธ ๅทฒๅฐ‡ **{0}** ๅŠ ๅ…ฅ {1} ็š„ๆ’ญๆ”พๆธ…ๅ–ฎ [`{2}`]๏ผ", - "playerDropdown": "้ธๆ“‡่ฆ่ทณ่ฝ‰็š„ๆญŒๆ›ฒ ...", - "playerFilter": "้ธๆ“‡่ฆๅฅ—็”จ็š„้Ÿณๆ•ˆ ...", - "buttonBack": "ไธŠไธ€้ฆ–", - "buttonPause": "ๆšซๅœ", - "buttonResume": "็นผ็บŒ", - "buttonSkip": "่ทณ้Ž", - "buttonLeave": "้›ข้–‹", - "buttonLoop": "ๅพช็’ฐ", - "buttonVolumeUp": "้Ÿณ้‡+", - "buttonVolumeDown": "้Ÿณ้‡-", - "buttonVolumeMute": "้œ้Ÿณ", - "buttonVolumeUnmute": "ๅ–ๆถˆ้œ้Ÿณ", - "buttonAutoPlay": "่‡ชๅ‹•ๆ’ญๆ”พ", - "buttonShuffle": "้šจๆฉŸๆ’ญๆ”พ", - "buttonForward": "ๅฟซ่ฝ‰", - "buttonRewind": "ๅ€’่ฝ‰", - "buttonLyrics": "ๆญŒ่ฉž", - "nowplayingDesc": "**ๆญฃๅœจๆ’ญๆ”พ:**\n```{0}```", - "nowplayingField": "ๆŽฅไธ‹ไพ†:", - "nowplayingLink": "ๅœจ {0} ๆ”ถ่ฝ", - "connect": "ๅทฒ้€ฃ็ทš่‡ณ {0}", - "live": "็›ดๆ’ญ", - "playlistLoad": " ๐ŸŽถ ๅทฒๅฐ‡ๆ’ญๆ”พๆธ…ๅ–ฎ **{0}** ็š„ `{1}` ้ฆ–ๆญŒๆ›ฒๅŠ ๅ…ฅ้šŠๅˆ—ใ€‚", - "trackLoad": "ๅทฒๆ–ฐๅขž **[{0}](<{1}>)** - **{2}** (`{3}`) ไธฆ้–‹ๅง‹ๆ’ญๆ”พใ€‚\n", - "trackLoad_pos": "ๅทฒๆ–ฐๅขž **[{0}](<{1}>)** - **{2}** (`{3}`) ๅˆฐ้šŠๅˆ—ไฝ็ฝฎ **{4}**\n", - "searchTitle": "ๆœๅฐ‹้—œ้ตๅญ—: {0}", - "searchDesc": "โžฅ ๅนณๅฐ: {0} **{1}**\nโžฅ ็ตๆžœๆ•ธ: **{2}**\n\n{3}", - "searchWait": "่ซ‹้ธๆ“‡่ฆๅŠ ๅ…ฅ้šŠๅˆ—็š„ๆญŒๆ›ฒใ€‚", - "searchTimeout": "ๆœๅฐ‹้€พๆ™‚๏ผŒ่ซ‹็จๅพŒๅ†่ฉฆใ€‚", - "searchSuccess": "ๆญŒๆ›ฒๅทฒๅŠ ๅ…ฅ้šŠๅˆ—ใ€‚", - "queueTitle": "ๅณๅฐ‡ๆ’ญๆ”พ้šŠๅˆ—:", - "historyTitle": "ๆ’ญๆ”พๆญทๅฒ:", - "viewTitle": "้Ÿณๆจ‚้šŠๅˆ—", - "viewDesc": "**ๆญฃๅœจๆ’ญๆ”พ: [้ปžๆˆ‘]({0}) โฎฏ**\n{1}", - "viewFooter": "็ฌฌ {0}/{1} ้  | ็ธฝๆ™‚้•ท: {2}", - "pauseError": "ๆ’ญๆ”พๅ™จๅทฒๆšซๅœใ€‚", - "pauseVote": "{0} ๆŠ•็ฅจๆšซๅœไบ†ๆญŒๆ›ฒใ€‚[{1}/{2}]", - "paused": "`{0}` ๅทฒๆšซๅœๆ’ญๆ”พๅ™จใ€‚", - "resumeError": "ๆ’ญๆ”พๅ™จๆœชๆšซๅœใ€‚", - "resumeVote": "{0} ๆŠ•็ฅจ็นผ็บŒๆ’ญๆ”พใ€‚[{1}/{2}]", - "resumed": "`{0}` ๅทฒ็นผ็บŒๆ’ญๆ”พใ€‚", - "shuffleError": "่ซ‹ๅœจ้šŠๅˆ—ไธญๆ–ฐๅขžๆ›ดๅคšๆญŒๆ›ฒๅพŒๅ†้šจๆฉŸๆ’ญๆ”พใ€‚", - "shuffleVote": "{0} ๆŠ•็ฅจ้šจๆฉŸๆ’ญๆ”พ้šŠๅˆ—ใ€‚[{1}/{2}]", - "shuffled": "้šŠๅˆ—ๅทฒ้šจๆฉŸๆŽ’ๅบใ€‚", - "skipError": "ๆฒ’ๆœ‰ๆญŒๆ›ฒๅฏ่ทณ้Žใ€‚", - "skipVote": "{0} ๆŠ•็ฅจ่ทณ้ŽๆญŒๆ›ฒใ€‚[{1}/{2}]", - "skipped": "`{0}` ๅทฒ่ทณ้ŽๆญŒๆ›ฒใ€‚", - "backVote": "{0} ๆŠ•็ฅจๆ’ญๆ”พไธŠไธ€้ฆ–ๆญŒใ€‚[{1}/{2}]", - "backed": "`{0}` ๅทฒๅˆ‡ๆ›ๅˆฐไธŠไธ€้ฆ–ๆญŒๆ›ฒใ€‚", - "leaveVote": "{0} ๆŠ•็ฅจๅœๆญขๆ’ญๆ”พใ€‚[{1}/{2}]", - "left": "`{0}` ๅทฒๅœๆญขๆ’ญๆ”พๅ™จใ€‚", - "seek": "ๅทฒๅฐ‡ๆ’ญๆ”พๅ™จๅฎšไฝ่‡ณ **{0}**", - "repeat": "ๅพช็’ฐๆจกๅผๅทฒ่จญๅฎš็‚บ `{0}`", - "cleared": "ๅทฒๆธ…็ฉบ `{0}` ไธญ็š„ๆ‰€ๆœ‰ๆ›ฒ็›ฎ", - "removed": "ๅทฒๅพž้šŠๅˆ—ไธญ็งป้™ค `{0}` ้ฆ–ๆญŒๆ›ฒใ€‚", - "forward": "ๅทฒๅฟซ่ฝ‰่‡ณ **{0}**", - "rewind": "ๅทฒๅ€’่ฝ‰่‡ณ **{0}**", - "replay": "ๆญฃๅœจ้‡ๆ’ญ็›ฎๅ‰็š„ๆญŒๆ›ฒใ€‚", - "swapped": "`{0}` ่ˆ‡ `{1}` ๅทฒไบคๆ›ไฝ็ฝฎ", - "moved": "ๅทฒๅฐ‡ `{0}` ็งปๅ‹•ๅˆฐ `{1}`", - "autoplay": "่‡ชๅ‹•ๆ’ญๆ”พๆจกๅผ็พๅœจๆ˜ฏ **{0}**", - "notdj": "ๆ‚จไธๆ˜ฏ DJใ€‚็›ฎๅ‰ DJ ๆ˜ฏ {0}ใ€‚", - "djToMe": "ๆ‚จไธ่ƒฝๅฐ‡ DJ ่บซๅˆ†่ฝ‰็งป็ตฆ่‡ชๅทฑๆˆ–ๆฉŸๅ™จไบบใ€‚", - "djNotInChannel": "`{0}` ไธๅœจ่ชž้Ÿณ้ ป้“ไธญใ€‚", - "djswap": "ๆ‚จๅทฒๅฐ‡ DJ ่บซๅˆ†่ฝ‰็งป็ตฆ `{0}`ใ€‚", - "voicelinkQueueFull": "ๆŠฑๆญ‰๏ผŒ้šŠๅˆ—ๅทฒ้”ๆœ€ๅคง `{0}` ้ฆ–ๆญŒๆ›ฒ๏ผ", - "voicelinkOutofList": "่ซ‹่ผธๅ…ฅๆœ‰ๆ•ˆ็š„ๆ›ฒ็›ฎๅบ่™Ÿ๏ผ", - "voicelinkDuplicateTrack": "ๆŠฑๆญ‰๏ผŒๆญคๆ›ฒ็›ฎๅทฒๅœจ้šŠๅˆ—ไธญใ€‚", - "decodeError": "่งฃ็ขผๆช”ๆกˆๆ™‚็™ผ็”Ÿ้Œฏ่ชค๏ผ", - "invalidStartTime": "็„กๆ•ˆ็š„้–‹ๅง‹ๆ™‚้–“๏ผŒๅฟ…้ ˆๅœจ `00:00` ๅ’Œ `{0}` ไน‹้–“", - "invalidEndTime": "็„กๆ•ˆ็š„็ตๆŸๆ™‚้–“๏ผŒๅฟ…้ ˆๅœจ `00:00` ๅ’Œ `{0}` ไน‹้–“", - "invalidTimeOrder": "็ตๆŸๆ™‚้–“ไธ่ƒฝๅฐๆ–ผๆˆ–็ญ‰ๆ–ผ้–‹ๅง‹ๆ™‚้–“", - "setStageAnnounceTemplate": "ๅฎŒๆˆ๏ผๅพž็พๅœจ่ตท๏ผŒๆ‚จๆ‰€ๅœจ็š„่ชž้Ÿณ็‹€ๆ…‹ๅฐ‡ไพ็…งๆจกๆฟๅ‘ฝๅใ€‚ๅนพ็ง’ๅพŒๅณๅฏ็œ‹ๅˆฐๆ›ดๆ–ฐใ€‚", - "createSongRequestChannel": "ๅทฒๅปบ็ซ‹้ปžๆญŒ้ ป้“ ({0})๏ผๆ‚จๅฏไปฅๅœจ่ฉฒ้ ป้“่ผธๅ…ฅๆญŒๆ›ฒๅ็จฑๆˆ–้€ฃ็ตไพ†้ปžๆญŒ๏ผŒ็„ก้œ€ไฝฟ็”จๆฉŸๅ™จไบบๅ‰็ถดใ€‚" -} \ No newline at end of file + "common": { + "errors": { + "unknown": "โš ๏ธ ๅŸท่กŒๆŒ‡ไปคๆ™‚็™ผ็”Ÿ้Œฏ่ชค๏ผ่ซ‹็จๅพŒๅ†่ฉฆ๏ผŒๆˆ–ๅŠ ๅ…ฅๆˆ‘ๅ€‘็š„ Discord ไผบๆœๅ™จๅ–ๅพ—ๅ”ๅŠฉใ€‚", + "missingIntents": "ๆŠฑๆญ‰๏ผŒ็„กๆณ•ๅŸท่กŒๆญคๆŒ‡ไปค๏ผŒๅ› ็‚บ็ผบๅฐ‘ๅฟ…่ฆ็š„่ซ‹ๆฑ‚ intent๏ผš`({0})`ใ€‚" + }, + "status": { + "enabled": "ๅทฒๅ•Ÿ็”จ", + "disabled": "ๅทฒๅœ็”จ", + "live": "็›ดๆ’ญ" + } + }, + "player": { + "errors": { + "nodeReconnect": "่ซ‹ๅœจ็ฏ€้ปž้‡ๆ–ฐ้€ฃ็ทšๅพŒๅ†่ฉฆ๏ผ", + "noPlaySource": "ๆ‰พไธๅˆฐๅฏๆ’ญๆ”พ็š„ไพ†ๆบ๏ผ", + "noPlayer": "ๆญคไผบๆœๅ™จไธŠๆฒ’ๆœ‰ๆ‰พๅˆฐๆ’ญๆ”พๅ™จใ€‚", + "noTrackPlaying": "็›ฎๅ‰ๆฒ’ๆœ‰ๆญฃๅœจๆ’ญๆ”พ็š„ๆญŒๆ›ฒ", + "noTrackFound": "ๆœชๆ‰พๅˆฐๅฐๆ‡‰็š„ๆญŒๆ›ฒ๏ผŒ่ซ‹ๆไพ›ๆœ‰ๆ•ˆ้€ฃ็ตใ€‚" + }, + "dropdown": { + "trackSelect": "้ธๆ“‡่ฆ่ทณ่ฝ‰็š„ๆญŒๆ›ฒ ...", + "filterSelect": "้ธๆ“‡่ฆๅฅ—็”จ็š„้Ÿณๆ•ˆ ..." + }, + "buttons": { + "back": "ไธŠไธ€้ฆ–", + "pause": "ๆšซๅœ", + "resume": "็นผ็บŒ", + "skip": "ไธ‹ไธ€้ฆ–", + "leave": "้›ข้–‹", + "loop": "ๅพช็’ฐ", + "volumeUp": "้Ÿณ้‡+", + "volumeDown": "้Ÿณ้‡-", + "volumeMute": "้œ้Ÿณ", + "volumeUnmute": "ๅ–ๆถˆ้œ้Ÿณ", + "autoPlay": "่‡ชๅ‹•ๆ’ญๆ”พ", + "shuffle": "้šจๆฉŸๆ’ญๆ”พ", + "forward": "ๅฟซ่ฝ‰", + "rewind": "ๅ€’่ฝ‰", + "lyrics": "ๆญŒ่ฉž" + }, + "playback": { + "nowplayingDesc": "**ๆญฃๅœจๆ’ญๆ”พ:**\n```{0}```", + "nowplayingField": "ๆŽฅไธ‹ไพ†:", + "nowplayingLink": "ๅœจ {0} ๆ”ถ่ฝ", + "playlistLoad": " ๐ŸŽถ ๅทฒๅฐ‡ๆ’ญๆ”พๆธ…ๅ–ฎ **{0}** ็š„ `{1}` ้ฆ–ๆญŒๆ›ฒๅŠ ๅ…ฅ้šŠๅˆ—ใ€‚", + "trackLoad": "ๅทฒๆ–ฐๅขž **[{0}](<{1}>)** - **{2}** (`{3}`) ไธฆ้–‹ๅง‹ๆ’ญๆ”พใ€‚\n", + "trackLoadPos": "ๅทฒๆ–ฐๅขž **[{0}](<{1}>)** - **{2}** (`{3}`) ๅˆฐ้šŠๅˆ—ไฝ็ฝฎ **{4}**\n" + }, + "controls": { + "pause": { + "error": "ๆ’ญๆ”พๅ™จๅทฒๆšซๅœใ€‚", + "vote": "{0} ๆŠ•็ฅจๆšซๅœไบ†ๆญŒๆ›ฒใ€‚[{1}/{2}]", + "success": "`{0}` ๅทฒๆšซๅœๆ’ญๆ”พๅ™จใ€‚" + }, + "resume": { + "error": "ๆ’ญๆ”พๅ™จๆœชๆšซๅœใ€‚", + "vote": "{0} ๆŠ•็ฅจ็นผ็บŒๆ’ญๆ”พใ€‚[{1}/{2}]", + "success": "`{0}` ๅทฒ็นผ็บŒๆ’ญๆ”พใ€‚" + }, + "shuffle": { + "error": "่ซ‹ๅœจ้šŠๅˆ—ไธญๆ–ฐๅขžๆ›ดๅคšๆญŒๆ›ฒๅพŒๅ†้šจๆฉŸๆ’ญๆ”พใ€‚", + "vote": "{0} ๆŠ•็ฅจ้šจๆฉŸๆ’ญๆ”พ้šŠๅˆ—ใ€‚[{1}/{2}]", + "success": "้šŠๅˆ—ๅทฒ้šจๆฉŸๆŽ’ๅบใ€‚" + }, + "skip": { + "error": "ๆฒ’ๆœ‰ๆญŒๆ›ฒๅฏ่ทณ้Žใ€‚", + "vote": "{0} ๆŠ•็ฅจ่ทณ้ŽๆญŒๆ›ฒใ€‚[{1}/{2}]", + "success": "`{0}` ๅทฒ่ทณ้ŽๆญŒๆ›ฒใ€‚" + }, + "back": { + "vote": "{0} ๆŠ•็ฅจๆ’ญๆ”พไธŠไธ€้ฆ–ๆญŒใ€‚[{1}/{2}]", + "success": "`{0}` ๅทฒๅˆ‡ๆ›ๅˆฐไธŠไธ€้ฆ–ๆญŒๆ›ฒใ€‚" + }, + "leave": { + "vote": "{0} ๆŠ•็ฅจๅœๆญขๆ’ญๆ”พใ€‚[{1}/{2}]", + "success": "`{0}` ๅทฒๅœๆญขๆ’ญๆ”พๅ™จใ€‚" + }, + "seek": "ๅทฒๅฐ‡ๆ’ญๆ”พๅ™จๅฎšไฝ่‡ณ **{0}**", + "repeat": "ๅพช็’ฐๆจกๅผๅทฒ่จญๅฎš็‚บ `{0}`", + "forward": "ๅทฒๅฟซ่ฝ‰่‡ณ **{0}**", + "rewind": "ๅทฒๅ€’่ฝ‰่‡ณ **{0}**", + "replay": "ๆญฃๅœจ้‡ๆ’ญ็›ฎๅ‰็š„ๆญŒๆ›ฒใ€‚", + "autoplay": "่‡ชๅ‹•ๆ’ญๆ”พๆจกๅผ็พๅœจๆ˜ฏ **{0}**" + } + }, + "voice": { + "connection": { + "noChannel": "ๆœชๅตๆธฌๅˆฐ่ชž้Ÿณ้ ป้“๏ผŒ่ซ‹ๆไพ›ไธ€ๅ€‹ๆˆ–ๅ…ˆๅŠ ๅ…ฅ่ชž้Ÿณ้ ป้“ใ€‚", + "alreadyConnected": "ๅทฒ้€ฃ็ทš่‡ณ่ชž้Ÿณ้ ป้“ใ€‚", + "noPermission": "ๆŠฑๆญ‰๏ผๆˆ‘ๆฒ’ๆœ‰ๆฌŠ้™ๅŠ ๅ…ฅๆˆ–ๅœจๆ‚จ็š„่ชž้Ÿณ้ ป้“ไธญ็™ผ่จ€ใ€‚", + "notInChannel": "{0}๏ผŒๆ‚จๅฟ…้ ˆๅœจ {1} ๆ‰่ƒฝไฝฟ็”จ่ชž้ŸณๆŒ‡ไปคใ€‚ๅฆ‚ๆžœๆ‚จๅทฒๅœจ้ ป้“๏ผŒ่ซ‹้‡ๆ–ฐๅŠ ๅ…ฅ๏ผ", + "connect": "ๅทฒ้€ฃ็ทš่‡ณ {0}" + }, + "stageChannel": { + "setAnnounceTemplate": "ๅฎŒๆˆ๏ผๅพž็พๅœจ่ตท๏ผŒๆ‚จๆ‰€ๅœจ็š„่ชž้Ÿณ็‹€ๆ…‹ๅฐ‡ไพ็…งๆจกๆฟๅ‘ฝๅใ€‚ๅนพ็ง’ๅพŒๅณๅฏ็œ‹ๅˆฐๆ›ดๆ–ฐใ€‚" + } + }, + "permissions": { + "noCreatePermission": "ๆŠฑๆญ‰๏ผๆˆ‘ๆฒ’ๆœ‰ๆฌŠ้™ๅปบ็ซ‹้ปžๆญŒ้ ป้“ใ€‚", + "missingPosition": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ“ก่ƒฝๆ›ดๆ”นไฝ็ฝฎใ€‚", + "missingMode": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ“ก่ƒฝๅˆ‡ๆ›ๅพช็’ฐๆจกๅผใ€‚", + "missingQueue": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ“ก่ƒฝๅพž้šŠๅˆ—ไธญ็งป้™คๆ›ฒ็›ฎใ€‚", + "missingAutoPlay": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ“ก่ƒฝๅ•Ÿ็”จๆˆ–ๅœ็”จ่‡ชๅ‹•ๆ’ญๆ”พ๏ผ", + "missingFunction": "ๅชๆœ‰ DJ ๆˆ–็ฎก็†ๅ“ก่ƒฝไฝฟ็”จๆญคๅŠŸ่ƒฝใ€‚", + "notDj": "ๆ‚จไธๆ˜ฏ DJใ€‚็›ฎๅ‰ DJ ๆ˜ฏ {0}ใ€‚", + "djToSelf": "ๆ‚จไธ่ƒฝๅฐ‡ DJ ่บซๅˆ†่ฝ‰็งป็ตฆ่‡ชๅทฑๆˆ–ๆฉŸๅ™จไบบใ€‚", + "djNotInChannel": "`{0}` ไธๅœจ่ชž้Ÿณ้ ป้“ไธญใ€‚", + "djSwapped": "ๆ‚จๅทฒๅฐ‡ DJ ่บซๅˆ†่ฝ‰็งป็ตฆ `{0}`ใ€‚" + }, + "settings": { + "actions": { + "languageNotFound": "ๆœชๆ‰พๅˆฐ่ชž่จ€ๅŒ…๏ผ่ซ‹้ธๆ“‡ไธ€ๅ€‹็พๆœ‰็š„่ชž่จ€ๅŒ…ใ€‚", + "languageChanged": "ๅทฒๆˆๅŠŸๅˆ‡ๆ›็‚บ `{0}` ่ชž่จ€ๅŒ…ใ€‚", + "prefixSet": "ๅฎŒๆˆ๏ผๆˆ‘ๅœจไผบๆœๅ™จ็š„ๅ‰็ถด็พๅœจๆ˜ฏ `{0}`ใ€‚่ฉฆ่‘—่ผธๅ…ฅ `{1}ping` ๆธฌ่ฉฆไธ€ไธ‹ๅงใ€‚", + "djSet": "ๅทฒๅฐ‡ DJ ่จญๅฎš็‚บ {0}ใ€‚", + "queueModeSet": "ๅทฒๅฐ‡้šŠๅˆ—ๆจกๅผ่จญๅฎš็‚บ `{0}`ใ€‚", + "mode247": "24/7 ๆจกๅผ็พๅœจๆ˜ฏ `{0}`ใ€‚", + "bypassVote": "ๆ‚จๆ“ๆœ‰ `{0}` ๆŠ•็ฅจ็ณป็ตฑใ€‚", + "volumeSet": "ๅทฒๅฐ‡้Ÿณ้‡่จญๅฎš็‚บ `{0}`%", + "controllerToggled": "ๆ‚จ็พๅœจๅทฒ `{0}` ้Ÿณๆจ‚ๆŽงๅˆถๅ™จใ€‚", + "duplicateTrackToggled": "ๆ‚จๅทฒ `{0}` ้‡่ค‡ๆ›ฒ็›ฎ้˜ฒๆญขๅŠŸ่ƒฝใ€‚", + "controllerMsgToggled": "ๆ‚จ็พๅœจๅทฒ `{0}` ไพ†่‡ช้Ÿณๆจ‚ๆŽงๅˆถๅ™จ็š„่จŠๆฏใ€‚", + "silentMsgToggled": "ๆ‚จๅทฒ {0} ้œ้Ÿณ่จŠๆฏใ€‚" + }, + "menu": "ไผบๆœๅ™จ่จญๅฎš | {0}", + "basic": { + "title": "โค๏ธ ๅŸบๆœฌ่ณ‡่จŠ:", + "value": "```ๅ‰็ถด: {0}\n่ชž่จ€: {1}\n้Ÿณๆจ‚ๆŽงๅˆถๅ™จ: {2}\nDJ ่บซๅˆ†็ต„: @{3}\nๆŠ•็ฅจ็•ฅ้Ž: {4}\n24/7: {5}\n้ ่จญ้Ÿณ้‡: {6}%\nๆ’ญๆ”พๆ™‚้•ท: {7}```" + }, + "queue": { + "title": "๐Ÿ”— ้šŠๅˆ—่ณ‡่จŠ:", + "value": "```้šŠๅˆ—ๆจกๅผ: {0}\nๆœ€ๅคงๆญŒๆ›ฒๆ•ธ: {1}\n้‡่ค‡ๆ›ฒ็›ฎ: {2}```" + }, + "voice": { + "title": "๐ŸŽค ่ชž้Ÿณ็‹€ๆ…‹่ณ‡่จŠ:" + }, + "permissions": { + "title": "โœจ ๆฌŠ้™:", + "value": "```{0} ็ฎก็†ๅ“ก\n{1} ็ฎก็†ไผบๆœๅ™จ\n{2} ็ฎก็†้ ป้“\n{3} ็ฎก็†่จŠๆฏ```" + } + }, + "ping": { + "title1": "ๆฉŸๅ™จไบบ่ณ‡่จŠ:", + "title2": "ๆ’ญๆ”พๅ™จ่ณ‡่จŠ:", + "field1": "```ๅˆ†็‰‡ ID: {0}/{1}\nๅˆ†็‰‡ๅปถ้ฒ: {2:.3f}s {3}\nๅ€ๅŸŸ: {4}```", + "field2": "```็ฏ€้ปž: {0} - {1:.3f}s\nๆ’ญๆ”พๅ™จๆ•ธ: {2}\n่ชž้Ÿณๅ€ๅŸŸ: {3}```" + }, + "effects": { + "added": "ๅทฒๅฅ—็”จ `{0}` ๆ•ˆๆžœใ€‚", + "cleared": "้Ÿณๆ•ˆๅทฒๆธ…้™ค๏ผ", + "tagInUse": "่ฉฒ้Ÿณๆ•ˆๅทฒๅœจไฝฟ็”จไธญ๏ผ่ซ‹ไฝฟ็”จ /cleareffect <ๆจ™็ฑค> ็งป้™คใ€‚" + }, + "playlist": { + "view": { + "title": "{0} ็š„ๆ‰€ๆœ‰ๆ’ญๆ”พๆธ…ๅ–ฎ", + "headers": "ID:,ๆ™‚้•ท:,ๅ็จฑ:,ๆ›ฒ็›ฎๆ•ธ:", + "footer": "่ผธๅ…ฅ /playlist play [ๆ’ญๆ”พๆธ…ๅ–ฎ] ๅฐ‡ๅ…ถๅŠ ๅ…ฅ้šŠๅˆ—ใ€‚", + "detailTitle": "ๆ’ญๆ”พๆธ…ๅ–ฎๆชข่ฆ–ๅ™จ", + "detailDesc": "```ๅ็จฑ | ID: {0} | {1}\n็ธฝๆ›ฒ็›ฎ: {2}\nๆ“ๆœ‰่€…: {3}\n้กžๅž‹: {4}\n```", + "permsValue": "๐Ÿ“– ่ฎ€ๅ–: โœ“ โœ๐Ÿฝ ๅฏซๅ…ฅ: {0} ๐Ÿ—‘๏ธ ็งป้™ค: {1}", + "permsValue2": "๐Ÿ“– ่ฎ€ๅ–: {0}", + "trackList": "ๆ›ฒ็›ฎๅˆ—่กจ", + "footer2": "็ธฝๆŒ็บŒๆ™‚้–“๏ผš{0}", + "playlistSelect": "้ธๆ“‡่ฆๆŸฅ็œ‹็š„ๆ’ญๆ”พๆธ…ๅ–ฎ ..." + }, + "errors": { + "notFound": "ๆœชๆ‰พๅˆฐๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`]ใ€‚่ผธๅ…ฅ /playlist view ๆŸฅ็œ‹ๆ‰€ๆœ‰ๆ’ญๆ”พๆธ…ๅ–ฎใ€‚", + "noAccess": "ๆŠฑๆญ‰๏ผๆ‚จ็„กๆณ•ๅญ˜ๅ–ๆญคๆ’ญๆ”พๆธ…ๅ–ฎ๏ผ", + "noTrack": "ๆŠฑๆญ‰๏ผๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`] ไธญๆฒ’ๆœ‰ๆ›ฒ็›ฎใ€‚", + "notAllowed": "ๆญคๆŒ‡ไปคไธๆ”ฏๆดๅทฒ้€ฃ็ตๆˆ–ๅ…ฑไบซ็š„ๆ’ญๆ”พๆธ…ๅ–ฎใ€‚", + "nameOverLimit": "ๆŠฑๆญ‰๏ผๆ’ญๆ”พๆธ…ๅ–ฎๅ็จฑไธ่ƒฝ่ถ…้Ž 10 ๅ€‹ๅญ—ๅ…ƒใ€‚", + "sameName": "ๆŠฑๆญ‰๏ผ่ซ‹็‚บๆ’ญๆ”พๆธ…ๅ–ฎ้ธๆ“‡ๆ–ฐๅ็จฑใ€‚", + "deleteDefault": "ไธๅ…่จฑๅˆช้™ค้ ่จญๆ’ญๆ”พๆธ…ๅ–ฎใ€‚", + "limitReached": "ๆ‚จๆœ€ๅคšๅช่ƒฝๅปบ็ซ‹ `{0}` ๅ€‹ๆ’ญๆ”พๆธ…ๅ–ฎ๏ผ", + "exists": "ๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`] ๅทฒๅญ˜ๅœจใ€‚", + "invalidUrl": "่ซ‹่ผธๅ…ฅๆœ‰ๆ•ˆ็š„ๅ…ฌ้–‹ Spotify ๆˆ– YouTube ๆ’ญๆ”พๆธ…ๅ–ฎ้€ฃ็ตใ€‚", + "trackLimitReached": "ๅทฒ้”ๅˆฐไธŠ้™๏ผๆฏๅ€‹ๆ’ญๆ”พๆธ…ๅ–ฎๆœ€ๅคšๅช่ƒฝๆ–ฐๅขž `{0}` ้ฆ–ๆญŒๆ›ฒใ€‚", + "playlistLinkNotAllowed": "ไธๅ…่จฑไฝฟ็”จๆ’ญๆ”พๆธ…ๅ–ฎ้€ฃ็ตใ€‚", + "streamNotAllowed": "ไธๅ…่จฑๅฐ‡ไธฒๆตๅฝฑ็‰‡ๅŠ ๅ…ฅๆ’ญๆ”พๆธ…ๅ–ฎใ€‚", + "positionNotFound": "ๅœจๆ’ญๆ”พๆธ…ๅ–ฎ [`{1}`] ไธญๆ‰พไธๅˆฐไฝ็ฝฎ `{0}`๏ผ", + "streaming": "ไธๅ…่จฑๅฐ‡ไธฒๆตๅฝฑ็‰‡ๅŠ ๅ…ฅๆ’ญๆ”พๆธ…ๅ–ฎ๏ผ", + "track": "ๆ–ฐๅขžๆ›ฒ็›ฎๅˆฐๆ’ญๆ”พๆธ…ๅ–ฎๆ™‚็™ผ็”Ÿๅ•้กŒ๏ผ", + "trackLimited": "ๅทฒ้”ๅˆฐไธŠ้™๏ผๆ’ญๆ”พๆธ…ๅ–ฎๆœ€ๅคšๅช่ƒฝๆ–ฐๅขž {0} ้ฆ–ๆญŒๆ›ฒใ€‚", + "trackRepeated": "ๆญคๆ›ฒ็›ฎๅทฒๅœจๆ’ญๆ”พๆธ…ๅ–ฎไธญ๏ผ" + }, + "actions": { + "play": "ๅทฒๅฐ‡ๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`] ็š„ `{1}` ้ฆ–ๆญŒๆ›ฒๅŠ ๅ…ฅ้šŠๅˆ—ใ€‚", + "removed": "ๆ‚จๅทฒๅˆช้™คๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`]ใ€‚", + "created": "ๆ‚จๅทฒๅปบ็ซ‹ `{0}` ๆ’ญๆ”พๆธ…ๅ–ฎใ€‚่ผธๅ…ฅ /playlist view ๆŸฅ็œ‹่ฉณๆƒ…ใ€‚", + "renamed": "ๆ‚จๅทฒๅฐ‡ `{0}` ้‡ๆ–ฐๅ‘ฝๅ็‚บ `{1}`ใ€‚", + "trackRemoved": "๐Ÿ‘‹ ๅทฒๅพž {1} ็š„ๆ’ญๆ”พๆธ…ๅ–ฎ [`{2}`] ไธญ็งป้™ค **{0}**ใ€‚", + "cleared": "ๆ‚จๅทฒๆˆๅŠŸๆธ…็ฉบๆ’ญๆ”พๆธ…ๅ–ฎ [`{0}`]ใ€‚", + "trackAdded": "โค๏ธ ๅทฒๅฐ‡ **{0}** ๅŠ ๅ…ฅ {1} ็š„ๆ’ญๆ”พๆธ…ๅ–ฎ [`{2}`]๏ผ" + }, + "sharing": { + "sendErrorPlayer": "ๆŠฑๆญ‰๏ผๆ‚จไธ่ƒฝ้‚€่ซ‹่‡ชๅทฑใ€‚", + "sendErrorBot": "ๆŠฑๆญ‰๏ผๆ‚จไธ่ƒฝ้‚€่ซ‹ๆฉŸๅ™จไบบใ€‚", + "belongs": "ๆŠฑๆญ‰๏ผๆญคๆ’ญๆ”พๆธ…ๅ–ฎๅฑฌๆ–ผ <@{0}>ใ€‚", + "alreadyShared": "ๆŠฑๆญ‰๏ผๆญคๆ’ญๆ”พๆธ…ๅ–ฎๅทฒ่ˆ‡ {0} ๅˆ†ไบซใ€‚", + "alreadySent": "ๆŠฑๆญ‰๏ผๆ‚จไน‹ๅ‰ๅทฒ็ถ“้‚€่ซ‹้Ž่ฉฒไฝฟ็”จ่€…ใ€‚", + "noAccount": "{0} ๅฐšๆœชๅปบ็ซ‹ๆ’ญๆ”พๆธ…ๅ–ฎๅธณ่™Ÿใ€‚", + "invitationSent": "้‚€่ซ‹ๅทฒ็™ผ้€็ตฆ {0}ใ€‚" + }, + "inbox": { + "full": "ๆŠฑๆญ‰๏ผ{0} ็š„ๆ”ถไปถๅŒฃๅทฒๆปฟใ€‚", + "noMessages": "ๆ‚จ็š„ๆ”ถไปถๅŒฃไธญๆฒ’ๆœ‰่จŠๆฏใ€‚" + } + }, + "search": { + "noLinkSupport": "ๆœๅฐ‹ๆŒ‡ไปคไธๆ”ฏๆด้€ฃ็ต๏ผ", + "title": "ๆœๅฐ‹้—œ้ตๅญ—: {0}", + "desc": "โžฅ ๅนณๅฐ: {0} **{1}**\nโžฅ ็ตๆžœๆ•ธ: **{2}**\n\n{3}", + "wait": "่ซ‹้ธๆ“‡่ฆๅŠ ๅ…ฅ้šŠๅˆ—็š„ๆญŒๆ›ฒใ€‚", + "success": "ๆญŒๆ›ฒๅทฒๅŠ ๅ…ฅ้šŠๅˆ—ใ€‚" + }, + "voting": { + "voted": "ๆ‚จๅทฒๆŠ•็ฅจ๏ผ" + }, + "time": { + "formatError": "ๆ™‚้–“ๆ ผๅผ้Œฏ่ชคใ€‚็ฏ„ไพ‹: 2:42 ๆˆ– 12:39:31", + "invalidStartTime": "็„กๆ•ˆ็š„้–‹ๅง‹ๆ™‚้–“๏ผŒๅฟ…้ ˆๅœจ `00:00` ๅ’Œ `{0}` ไน‹้–“", + "invalidEndTime": "็„กๆ•ˆ็š„็ตๆŸๆ™‚้–“๏ผŒๅฟ…้ ˆๅœจ `00:00` ๅ’Œ `{0}` ไน‹้–“" + }, + "lyrics": { + "notFound": "ๆœชๆ‰พๅˆฐๆญŒ่ฉžใ€‚่ผธๅ…ฅ /lyrics <ๆญŒๆ›ฒๅ็จฑ> <ไฝœ่€…> ๆŸฅๆ‰พๆญŒ่ฉžใ€‚" + }, + "queue": { + "management": { + "title": "ๅณๅฐ‡ๆ’ญๆ”พ้šŠๅˆ—:", + "cleared": "ๅทฒๆธ…็ฉบ `{0}` ไธญ็š„ๆ‰€ๆœ‰ๆ›ฒ็›ฎ", + "removed": "ๅทฒๅพž้šŠๅˆ—ไธญ็งป้™ค `{0}` ้ฆ–ๆญŒๆ›ฒใ€‚", + "swapped": "`{0}` ่ˆ‡ `{1}` ๅทฒไบคๆ›ไฝ็ฝฎ", + "moved": "ๅทฒๅฐ‡ `{0}` ็งปๅ‹•ๅˆฐ `{1}`" + }, + "view": { + "historyTitle": "ๆ’ญๆ”พๆญทๅฒ:", + "title": "้Ÿณๆจ‚้šŠๅˆ—", + "desc": "**ๆญฃๅœจๆ’ญๆ”พ: [้ปžๆˆ‘]({0}) โฎฏ**\n{1}" + }, + "errors": { + "queueFull": "ๆŠฑๆญ‰๏ผŒ้šŠๅˆ—ๅทฒ้”ๆœ€ๅคง `{0}` ้ฆ–ๆญŒๆ›ฒ๏ผ", + "outOfList": "่ซ‹่ผธๅ…ฅๆœ‰ๆ•ˆ็š„ๆ›ฒ็›ฎๅบ่™Ÿ๏ผ", + "duplicateTrack": "ๆŠฑๆญ‰๏ผŒๆญคๆ›ฒ็›ฎๅทฒๅœจ้šŠๅˆ—ไธญใ€‚" + } + }, + "songRequest": { + "channelCreated": "ๅทฒๅปบ็ซ‹้ปžๆญŒ้ ป้“ ({0})๏ผๆ‚จๅฏไปฅๅœจ่ฉฒ้ ป้“่ผธๅ…ฅๆญŒๆ›ฒๅ็จฑๆˆ–้€ฃ็ตไพ†้ปžๆญŒ๏ผŒ็„ก้œ€ไฝฟ็”จๆฉŸๅ™จไบบๅ‰็ถดใ€‚" + }, + "pagination": { + "prev": "ไธŠไธ€้ ", + "next": "ไธ‹ไธ€้ ", + "play": "ๆ’ญๆ”พ", + "share": "ๅˆ†ไบซ", + "export": "ๅŒฏๅ‡บ", + "delete": "ๅˆช้™ค", + "page": { + "title": "่ผธๅ…ฅ้ ็ขผ", + "field": "้ ็ขผ" + } + } +} diff --git a/local_langs/es-419.json b/local_langs/es-419.json index 6aed14d..c5f616e 100644 --- a/local_langs/es-419.json +++ b/local_langs/es-419.json @@ -109,7 +109,7 @@ "value": "valor", "Play the specific track from your custom playlist.": "Reproduce la pista especรญfica de tu lista de reproducciรณn personalizada.", "view": "ver", - "List all your playlist and all songs in your favourite playlist.": "Lista todas tus listas de reproducciรณn y canciones en tu lista favorita.", + "List all your playlists and all songs in your favourite playlist.": "Lista todas tus listas de reproducciรณn y canciones en tu lista favorita.", "create": "crear", "Create your custom playlist.": "Crea tu lista de reproducciรณn personalizada.", "Give a name to your playlist.": "Dale un nombre a tu lista de reproducciรณn.", diff --git a/local_langs/es-ES.json b/local_langs/es-ES.json index 4580dba..831a9a3 100644 --- a/local_langs/es-ES.json +++ b/local_langs/es-ES.json @@ -109,7 +109,7 @@ "value": "valor", "Play the specific track from your custom playlist.": "Reproduce la pista especรญfica de tu lista de reproducciรณn personalizada.", "view": "ver", - "List all your playlist and all songs in your favourite playlist.": "Lista todas tus listas de reproducciรณn y canciones en tu lista favorita.", + "List all your playlists and all songs in your favourite playlist.": "Lista todas tus listas de reproducciรณn y canciones en tu lista favorita.", "create": "crear", "Create your custom playlist.": "Crea tu lista de reproducciรณn personalizada.", "Give a name to your playlist.": "Dale un nombre a tu lista de reproducciรณn.", diff --git a/local_langs/zh-CN.json b/local_langs/zh-CN.json index fabe021..43e37a6 100644 --- a/local_langs/zh-CN.json +++ b/local_langs/zh-CN.json @@ -109,7 +109,7 @@ "value": "ๅ€ผ", "Play the specific track from your custom playlist.": "ๆ’ญๆ”พๆ‚จ่‡ชๅฎšไน‰ๆ’ญๆ”พๅˆ—่กจไธญ็š„ๆŒ‡ๅฎšๆญŒๆ›ฒใ€‚", "view": "ๆŸฅ็œ‹", - "List all your playlist and all songs in your favourite playlist.": "ๅˆ—ๅ‡บๆ‚จๆ‰€ๆœ‰็š„ๆ’ญๆ”พๅˆ—่กจๅ’Œๆœ€ๅ–œๆฌข็š„ๆ’ญๆ”พๅˆ—่กจไธญ็š„ๆ‰€ๆœ‰ๆญŒๆ›ฒใ€‚", + "List all your playlists and all songs in your favourite playlist.": "ๅˆ—ๅ‡บๆ‚จๆ‰€ๆœ‰็š„ๆ’ญๆ”พๅˆ—่กจๅ’Œๆœ€ๅ–œๆฌข็š„ๆ’ญๆ”พๅˆ—่กจไธญ็š„ๆ‰€ๆœ‰ๆญŒๆ›ฒใ€‚", "create": "ๅˆ›ๅปบ", "Create your custom playlist.": "ๅˆ›ๅปบ่‡ชๅฎšไน‰ๆ’ญๆ”พๅˆ—่กจใ€‚", "Give a name to your playlist.": "ไธบๆ‚จ็š„ๆ’ญๆ”พๅˆ—่กจๅ‘ฝๅใ€‚", diff --git a/local_langs/zh-TW.json b/local_langs/zh-TW.json index 35fbdae..b0ee859 100644 --- a/local_langs/zh-TW.json +++ b/local_langs/zh-TW.json @@ -109,7 +109,7 @@ "value": "ๅ€ผ", "Play the specific track from your custom playlist.": "ๆ’ญๆ”พๆ‚จ่‡ชๅฎš็พฉๆ’ญๆ”พๅˆ—่กจไธญ็š„ๆŒ‡ๅฎšๆญŒๆ›ฒใ€‚", "view": "ๆŸฅ็œ‹", - "List all your playlist and all songs in your favourite playlist.": "ๅˆ—ๅ‡บๆ‚จๆ‰€ๆœ‰็š„ๆ’ญๆ”พๅˆ—่กจๅ’Œๆ‚จๆœ€ๆ„›็š„ๆ’ญๆ”พๅˆ—่กจไธญ็š„ๆ‰€ๆœ‰ๆญŒๆ›ฒใ€‚", + "List all your playlists and all songs in your favourite playlist.": "ๅˆ—ๅ‡บๆ‚จๆ‰€ๆœ‰็š„ๆ’ญๆ”พๅˆ—่กจๅ’Œๆ‚จๆœ€ๆ„›็š„ๆ’ญๆ”พๅˆ—่กจไธญ็š„ๆ‰€ๆœ‰ๆญŒๆ›ฒใ€‚", "create": "ๅ‰ตๅปบ", "Create your custom playlist.": "ๅ‰ตๅปบๆ‚จ่‡ชๅฎš็พฉ็š„ๆ’ญๆ”พๅˆ—่กจใ€‚", "Give a name to your playlist.": "็ตฆๆ‚จ็š„ๆ’ญๆ”พๅˆ—่กจๅ‘ฝๅใ€‚", diff --git a/main.py b/main.py index ed9a483..a813856 100644 --- a/main.py +++ b/main.py @@ -27,42 +27,48 @@ import os import aiohttp import update import logging -import voicelink import function as func from discord.ext import commands -from ipc import IPCClient -from motor.motor_asyncio import AsyncIOMotorClient from logging.handlers import TimedRotatingFileHandler -from addons import Settings +from voicelink import Config, LangHandler, MongoDBHandler, IPCClient, VoicelinkException +from voicelink.utils import dispatch_message class Translator(discord.app_commands.Translator): + MISSING_TRANSLATOR: dict[str, list[str]] = {} + async def load(self): func.logger.info("Loaded Translator") - + async def unload(self): func.logger.info("Unload Translator") + + async def translate( + self, + string: discord.app_commands.locale_str, + locale: discord.Locale, + context: discord.app_commands.TranslationContext + ) -> str | None: + locale_key = str(locale).upper() + local_translations = LangHandler._local_langs.get(locale_key) + if not local_translations: + return None - async def translate(self, string: discord.app_commands.locale_str, locale: discord.Locale, context: discord.app_commands.TranslationContext): - locale_key = str(locale) - - if locale_key in func.LOCAL_LANGS: - translated_text = func.LOCAL_LANGS[locale_key].get(string.message) - - if translated_text is None: - missing_translations = func.MISSING_TRANSLATOR.setdefault(locale_key, []) - if string.message not in missing_translations: - missing_translations.append(string.message) - + translated_text = local_translations.get(string.message) + if translated_text is not None: return translated_text + missing = self.MISSING_TRANSLATOR.setdefault(locale_key, []) + if string.message not in missing: + missing.append(string.message) + return None class Vocard(commands.Bot): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.ipc: IPCClient + self.ipc_client: IPCClient async def on_message(self, message: discord.Message, /) -> None: # Ignore messages from bots or DMs @@ -77,7 +83,7 @@ class Vocard(commands.Bot): return await message.channel.send(f"My prefix is `{prefix}`") # Fetch guild settings and check if the mesage is in the music request channel - settings = await func.get_settings(message.guild.id) + settings = await MongoDBHandler.get_settings(message.guild.id) if settings and (request_channel := settings.get("music_request_channel")): if message.channel.id == request_channel.get("text_channel_id"): ctx = await self.get_context(message) @@ -91,34 +97,16 @@ class Vocard(commands.Bot): await cmd(ctx, query=attachment.url) except Exception as e: - await func.send(ctx, str(e), ephemeral=True) + await dispatch_message(ctx, str(e), ephemeral=True) finally: return await message.delete() await self.process_commands(message) - async def connect_db(self) -> None: - if not ((db_name := func.settings.mongodb_name) and (db_url := func.settings.mongodb_url)): - raise Exception("MONGODB_NAME and MONGODB_URL can't not be empty in settings.json") - - try: - func.MONGO_DB = AsyncIOMotorClient(host=db_url) - await func.MONGO_DB.server_info() - func.logger.info(f"Successfully connected to [{db_name}] MongoDB!") - - except Exception as e: - func.logger.error("Not able to connect MongoDB! Reason:", exc_info=e) - exit() - - func.SETTINGS_DB = func.MONGO_DB[db_name]["Settings"] - func.USERS_DB = func.MONGO_DB[db_name]["Users"] - async def setup_hook(self) -> None: - func.langs_setup() - # Connecting to MongoDB - await self.connect_db() + await MongoDBHandler.init(bot_config.mongodb_url, bot_config.mongodb_name) # Set translator await self.tree.set_translator(Translator()) @@ -132,32 +120,34 @@ class Vocard(commands.Bot): except Exception as e: func.logger.error(f"Something went wrong while loading {module[:-3]} cog.", exc_info=e) - self.ipc = IPCClient(self, **func.settings.ipc_client) - if func.settings.ipc_client.get("enable", False): + self.ipc_client: IPCClient = IPCClient(self, **bot_config.ipc_client) + if bot_config.ipc_client.get("enable", False): try: - await self.ipc.connect() + await self.ipc_client.connect() except Exception as e: func.logger.error(f"Cannot connected to dashboard! - Reason: {e}") # Update version tracking - if not func.settings.version or func.settings.version != update.__version__: + if not bot_config.version or bot_config.version != update.__version__: await self.tree.sync() func.update_json("settings.json", new_data={"version": update.__version__}) - for locale_key, values in func.MISSING_TRANSLATOR.items(): + + for locale_key, values in self.tree.translator.MISSING_TRANSLATOR.items(): func.logger.warning(f'Missing translation for "{", ".join(values)}" in "{locale_key}"') + self.tree.translator.MISSING_TRANSLATOR.clear() async def on_ready(self): func.logger.info("------------------") func.logger.info(f"Logging As {self.user}") func.logger.info(f"Bot ID: {self.user.id}") func.logger.info("------------------") + func.logger.info(f"Vocard Version: {update.__version__}") func.logger.info(f"Discord Version: {discord.__version__}") func.logger.info(f"Python Version: {sys.version}") func.logger.info("------------------") - func.settings.client_id = self.user.id - func.LOCAL_LANGS.clear() - func.MISSING_TRANSLATOR.clear() + bot_config.client_id = self.user.id + LangHandler._local_langs.clear() async def on_command_error(self, ctx: commands.Context, exception, /) -> None: error = getattr(exception, 'original', exception) @@ -178,12 +168,12 @@ class Vocard(commands.Bot): description += f"**Aliases:**\n`{', '.join([f'{ctx.prefix}{alias}' for alias in ctx.command.aliases])}`\n\n" description += f"**Description:**\n{ctx.command.help}\n\u200b" - embed = discord.Embed(description=description, color=func.settings.embed_color) - embed.set_footer(icon_url=ctx.me.display_avatar.url, text=f"More Help: {func.settings.invite_link}") + embed = discord.Embed(description=description, color=bot_config.embed_color) + embed.set_footer(icon_url=ctx.me.display_avatar.url, text=f"More Help: {bot_config.invite_link}") return await ctx.reply(embed=embed) - elif not issubclass(error.__class__, voicelink.VoicelinkException): - error = await func.get_lang(ctx.guild.id, "unknownException") + func.settings.invite_link + elif not issubclass(error.__class__, VoicelinkException): + error = await Lang_handler.get_lang(ctx.guild.id, "common.errors.unknown") + bot_config.invite_link func.logger.error(f"An unexpected error occurred in the {ctx.command.name} command on the {ctx.guild.name}({ctx.guild.id}).", exc_info=exception) try: @@ -206,13 +196,14 @@ class CommandCheck(discord.app_commands.CommandTree): return True async def get_prefix(bot: commands.Bot, message: discord.Message) -> str: - settings = await func.get_settings(message.guild.id) - return settings.get("prefix", func.settings.bot_prefix) + settings = await MongoDBHandler.get_settings(message.guild.id) + return settings.get("prefix", bot_config.bot_prefix) # Loading settings and logger -func.settings = Settings(func.open_json("settings.json")) +bot_config = Config(func.open_json("settings.json")) +Lang_handler = LangHandler.init() -LOG_SETTINGS = func.settings.logging +LOG_SETTINGS = bot_config.logging if (LOG_FILE := LOG_SETTINGS.get("file", {})).get("enable", True): log_path = os.path.abspath(LOG_FILE.get("path", "./logs")) if not os.path.exists(log_path): @@ -229,9 +220,10 @@ for log_name, log_level in LOG_SETTINGS.get("level", {}).items(): # Setup the bot object intents = discord.Intents.default() -intents.message_content = False if func.settings.bot_prefix is None else True -intents.members = func.settings.ipc_client.get("enable", False) +intents.message_content = False if bot_config.bot_prefix is None else True +intents.members = bot_config.ipc_client.get("enable", False) intents.voice_states = True +intents.presences = False bot = Vocard( command_prefix=get_prefix, @@ -245,4 +237,4 @@ bot = Vocard( if __name__ == "__main__": update.check_version(with_msg=True) - bot.run(func.settings.token, root_logger=True) + bot.run(bot_config.token, root_logger=True) diff --git a/requirements.txt b/requirements.txt index 30dac47..0cbe8f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -discord.py==2.5.2 +discord.py==2.6.3 motor==3.6.0 dnspython==2.2.1 tldextract==3.2.1 diff --git a/settings Example.json b/settings Example.json index 3a359b6..af423cb 100644 --- a/settings Example.json +++ b/settings Example.json @@ -40,6 +40,7 @@ "bot_access_user": [], "embed_color":"0xb3b3b3", "default_max_queue": 1000, + "default_search_platform": "youtube", "lyrics_platform": "lrclib", "ipc_client": { "host": "127.0.0.1", @@ -48,6 +49,11 @@ "secure": false, "enable": false }, + "playlist_settings": { + "max_playlist": 5, + "max_tracks_per_playlist": 500, + "default_playlist_name": "Favourite" + }, "sources_settings": { "youtube": { "emoji": "<:youtube:826661982760992778>", @@ -121,27 +127,27 @@ { "back": { "emoji": "โฎ๏ธ", - "label": "@@t_buttonBack@@" + "label": "@@t_player.buttons.back@@" }, "play-pause": { "states": { "pause": { "emoji": "โธ๏ธ", - "label": "@@t_buttonPause@@" + "label": "@@t_player.buttons.pause@@" }, "resume": { "emoji": "โ–ถ๏ธ", - "label": "@@t_buttonResume@@" + "label": "@@t_player.buttons.resume@@" } } }, "skip": { "emoji": "โญ๏ธ", - "label": "@@t_buttonSkip@@" + "label": "@@t_player.buttons.skip@@" }, "stop": { "emoji": "โน๏ธ", - "label": "@@t_buttonLeave@@", + "label": "@@t_player.buttons.leave@@", "style": "red" }, "add-fav": { @@ -151,7 +157,7 @@ }, { "tracks": { - "label": "@@t_playerDropdown@@", + "label": "@@t_player.dropdown.trackSelect@@", "max_options": 10 } } diff --git a/update.py b/update.py index a6452ba..330125e 100644 --- a/update.py +++ b/update.py @@ -31,7 +31,7 @@ import subprocess from io import BytesIO ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) -__version__ = "v2.7.2" +__version__ = "v2.7.3b1" # URLs for update and migration PYTHON_CMD_NAME = os.path.basename(sys.executable) diff --git a/views/list.py b/views/list.py deleted file mode 100644 index 7ee4469..0000000 --- a/views/list.py +++ /dev/null @@ -1,120 +0,0 @@ -"""MIT License - -Copyright (c) 2023 - present Vocard Development - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -import discord -import function as func - -from math import ceil -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from voicelink import Player, Track - -class ListView(discord.ui.View): - def __init__( - self, - player: "Player", - author: discord.Member, - is_queue: bool = True - ) -> None: - super().__init__(timeout=60) - self.player: Player = player - - self.is_queue: bool = is_queue - self.tracks: list[Track] = player.queue.tracks() if is_queue else player.queue.history() - self.response: discord.Message = None - - if not is_queue: - self.tracks.reverse() - self.author: discord.Member = author - - self.page: int = ceil(len(self.tracks) / 7) - self.current_page: int = 1 - - try: - self.time: str = func.time(sum([track.length for track in self.tracks])) - except Exception as _: - self.time = "โˆž" - - async def on_timeout(self) -> None: - for child in self.children: - child.disabled = True - try: - await self.response.edit(view=self) - except: - pass - - async def on_error(self, error, item, interaction) -> None: - return - - async def interaction_check(self, interaction: discord.Interaction) -> bool: - return interaction.user == self.author - - async def build_embed(self) -> discord.Embed: - offset: int = self.current_page * 7 - tracks: list[Track] = self.tracks[(offset-7):offset] - texts = await func.get_lang(self.author.guild.id, "viewTitle", "viewDesc", "nowplayingDesc", "live", "queueTitle", "historyTitle", "viewFooter") - - embed = discord.Embed(title=texts[0], color=func.settings.embed_color) - embed.description=texts[1].format(self.player.current.uri, f"```{self.player.current.title}```") if self.player.current else texts[2].format("None") - - embed.description += "\n**" + (texts[4] if self.is_queue else texts[5]) + "**\n" + "\n".join([ - f"{track.emoji} `{i:>2}.` `[" + (texts[3] if track.is_stream else func.time(track.length)) + f']` [{func.truncate_string(track.title)}]({track.uri})' + (track.requester.mention) - for i, track in enumerate(tracks, start=offset-6) - ]) - embed.set_footer(text=texts[6].format(self.current_page, self.page, self.time)) - - return embed - - @discord.ui.button(label='<<', style=discord.ButtonStyle.grey) - async def fast_back_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: - if self.current_page != 1: - self.current_page = 1 - return await interaction.response.edit_message(embed=await self.build_embed()) - await interaction.response.defer() - - @discord.ui.button(label='Back', style=discord.ButtonStyle.blurple) - async def back_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: - if self.current_page > 1: - self.current_page -= 1 - return await interaction.response.edit_message(embed=await self.build_embed()) - await interaction.response.defer() - - @discord.ui.button(label='Next', style=discord.ButtonStyle.blurple) - async def next_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: - if self.current_page < self.page: - self.current_page += 1 - return await interaction.response.edit_message(embed=await self.build_embed()) - await interaction.response.defer() - - @discord.ui.button(label='>>', style=discord.ButtonStyle.grey) - async def fast_next_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: - if self.current_page != self.page: - self.current_page = self.page - return await interaction.response.edit_message(embed=await self.build_embed()) - await interaction.response.defer() - - @discord.ui.button(emoji='๐Ÿ—‘๏ธ', style=discord.ButtonStyle.red) - async def stop_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: - await self.response.delete() - self.stop() \ No newline at end of file diff --git a/views/lyrics.py b/views/lyrics.py deleted file mode 100644 index b520313..0000000 --- a/views/lyrics.py +++ /dev/null @@ -1,110 +0,0 @@ -"""MIT License - -Copyright (c) 2023 - present Vocard Development - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -import discord -import function as func - -class LyricsDropdown(discord.ui.Select): - def __init__(self, langs: list[str]) -> None: - self.view: LyricsView - - super().__init__( - placeholder="Select A Lyrics Translation", - min_values=1, max_values=1, - options=[discord.SelectOption(label=lang) for lang in langs], - custom_id="selectLyricsLangs" - ) - - async def callback(self, interaction: discord.Interaction) -> None: - self.view.lang = self.values[0] - self.view.current_page = 1 - self.view.pages = len(self.view.source.get(self.values[0])) - await interaction.response.edit_message(embed=self.view.build_embed()) - -class LyricsView(discord.ui.View): - def __init__(self, name: str, source: dict, author: discord.Member) -> None: - super().__init__(timeout=60) - - self.name: str = name - self.source: dict[str, list[str]] = source - self.lang: list[str] = list(source.keys())[0] - self.author: discord.Member = author - - self.response: discord.Message = None - self.pages: int = len(self.source.get(self.lang)) - self.current_page: int = 1 - self.add_item(LyricsDropdown(list(source.keys()))) - - async def interaction_check(self, interaction: discord.Interaction) -> bool: - return interaction.user == self.author - - async def on_timeout(self) -> None: - for child in self.children: - child.disabled = True - try: - await self.response.edit(view=self) - except: - pass - - async def on_error(self, error, item, interaction) -> None: - return - - def build_embed(self) -> discord.Embed: - chunk = self.source.get(self.lang)[self.current_page - 1] - embed=discord.Embed(description=chunk, color=func.settings.embed_color) - embed.set_author(name=f"Searching Query: {self.name}", icon_url=self.author.display_avatar.url) - embed.set_footer(text=f"Page: {self.current_page}/{self.pages}") - return embed - - @discord.ui.button(label='<<', style=discord.ButtonStyle.grey) - async def fast_back_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: - if self.current_page != 1: - self.current_page = 1 - return await interaction.response.edit_message(embed=self.build_embed()) - await interaction.response.defer() - - @discord.ui.button(label='Back', style=discord.ButtonStyle.blurple) - async def back_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: - if self.current_page > 1: - self.current_page -= 1 - return await interaction.response.edit_message(embed=self.build_embed()) - await interaction.response.defer() - - @discord.ui.button(label='Next', style=discord.ButtonStyle.blurple) - async def next_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: - if self.current_page < self.pages: - self.current_page += 1 - return await interaction.response.edit_message(embed=self.build_embed()) - await interaction.response.defer() - - @discord.ui.button(label='>>', style=discord.ButtonStyle.grey) - async def fast_next_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: - if self.current_page != self.pages: - self.current_page = self.pages - return await interaction.response.edit_message(embed=self.build_embed()) - await interaction.response.defer() - - @discord.ui.button(emoji='๐Ÿ—‘๏ธ', style=discord.ButtonStyle.red) - async def stop_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: - await self.response.delete() - self.stop() \ No newline at end of file diff --git a/views/playlist.py b/views/playlist.py deleted file mode 100644 index d78ad31..0000000 --- a/views/playlist.py +++ /dev/null @@ -1,164 +0,0 @@ -"""MIT License - -Copyright (c) 2023 - present Vocard Development - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -import discord -import function as func - -from math import ceil -from tldextract import extract -from typing import Any, TYPE_CHECKING - -if TYPE_CHECKING: - from voicelink import Track - -class Select_playlist(discord.ui.Select): - def __init__(self, results): - self.view: PlaylistView - - super().__init__( - placeholder="Select a playlist to view ..", - custom_id="selector", - options=[discord.SelectOption(emoji='๐ŸŒŽ', label='All Playlist')] + - [ - discord.SelectOption(emoji=playlist['emoji'], label=f'{index}. {playlist["name"]}', description=f"{playlist['time']} ยท {playlist['type']}") - for index, playlist in enumerate(results, start=1) if playlist['type'] != 'error' - ] - ) - - async def callback(self, interaction: discord.Interaction) -> None: - if self.values[0] == 'All Playlist': - self.view.current = None - self.view.toggle_btn(True) - return await interaction.response.edit_message(embed=self.view.viewEmbed, view=self.view) - - self.view.current = self.view.results[int(self.values[0].split(". ")[0]) - 1] - self.view.page = ceil(len(self.view.current['tracks']) / 7) - self.view.current_page = 1 - self.view.toggle_btn(False) - await interaction.response.edit_message(embed=await self.view.build_embed(), view=self.view) - -class PlaylistView(discord.ui.View): - def __init__( - self, - viewEmbed: discord.Embed, - results: list[dict[str, Any]], - author: discord.Message - ) -> None: - super().__init__(timeout=60) - - self.viewEmbed: discord.Embed = viewEmbed - self.results: list[dict[str, Any]] = results - self.author: discord.Member = author - self.response: discord.Message = None - - self.current: dict[str, Any] = None - self.page: int = 0 - self.current_page: int = 1 - - self.add_item(Select_playlist(results)) - - async def interaction_check(self, interaction: discord.Interaction) -> bool: - return interaction.user == self.author - - async def on_error(self, error, item, interaction) -> None: - return - - def toggle_btn(self, action: bool) -> None: - for child in self.children: - if child.custom_id not in ("delete", "selector"): - child.disabled = action - - async def build_embed(self) -> discord.Embed: - offset: int = self.current_page * 7 - tracks: list[Track] = self.current['tracks'][(offset-7):offset] - texts = await func.get_lang(self.author.guild.id, "playlistView", "playlistViewDesc", "settingsPermTitle", "playlistViewPermsValue", "playlistViewPermsValue2", "playlistViewTrack", "playlistNoTrack", "playlistViewPage") - - embed = discord.Embed(title=texts[0], color=func.settings.embed_color) - embed.description = texts[1].format(self.current['name'], self.current['id'], len(self.current['tracks']), owner if (owner := self.current.get('owner')) else f"{self.author.id} (You)", self.current['type']) + "\n" - - perms = self.current['perms'] - if self.current['type'] == 'share': - embed.description += texts[2] + "\n" + texts[3].format('โœ“' if 'write' in perms and self.author.id in perms['write'] else 'โœ˜', 'โœ“' if 'remove' in perms and self.author.id in perms['remove'] else 'โœ˜') - else: - embed.description += texts[2] + "\n" + texts[4].format(', '.join(f'<@{user}>' for user in perms['read'])) - - embed.description += f"\n\n**{texts[5]}:**\n" - if tracks: - if self.current.get("type") == "playlist": - embed.description += "\n".join(f"{func.get_source(track['sourceName'], 'emoji')} `{index:>2}.` `[{func.time(track['length'])}]` [{func.truncate_string(track['title'])}]({track['uri']})" for index, track in enumerate(tracks, start=offset - 6)) - else: - embed.description += '\n'.join(f"{func.get_source(extract(track.info['uri']).domain, 'emoji')} `{index:>2}.` `[{func.time(track.length)}]` [{func.truncate_string(track.title)}]({track.uri})" for index, track in enumerate(tracks, start=offset - 6)) - else: - embed.description += texts[6].format(self.current['name']) - - embed.set_footer(text=texts[7].format(self.current_page, self.page, self.current['time'])) - return embed - - async def on_timeout(self) -> None: - for child in self.children: - child.disabled = True - try: - await self.response.edit(view=self) - except: - pass - - @discord.ui.button(label='<<', style=discord.ButtonStyle.grey, disabled=True) - async def fast_back_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: - if not self.current: - return - if self.current_page != 1: - self.current_page = 1 - return await interaction.response.edit_message(embed=await self.build_embed()) - await interaction.response.defer() - - @discord.ui.button(label='Back', style=discord.ButtonStyle.blurple, disabled=True) - async def back_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: - if not self.current: - return - if self.current_page > 1: - self.current_page -= 1 - return await interaction.response.edit_message(embed=await self.build_embed()) - await interaction.response.defer() - - @discord.ui.button(label='Next', style=discord.ButtonStyle.blurple, disabled=True) - async def next_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: - if not self.current: - return - if self.current_page < self.page: - self.current_page += 1 - return await interaction.response.edit_message(embed=await self.build_embed()) - await interaction.response.defer() - - @discord.ui.button(label='>>', style=discord.ButtonStyle.grey, disabled=True) - async def fast_next_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: - if not self.current: - return - if self.current_page != self.page: - self.current_page = self.page - return await interaction.response.edit_message(embed=await self.build_embed()) - await interaction.response.defer() - - @discord.ui.button(emoji='๐Ÿ—‘๏ธ', custom_id="delete", style=discord.ButtonStyle.red) - async def stop_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: - await self.response.delete() - self.stop() \ No newline at end of file diff --git a/voicelink/__init__.py b/voicelink/__init__.py index bedf3f3..c839b37 100644 --- a/voicelink/__init__.py +++ b/voicelink/__init__.py @@ -21,18 +21,22 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -__version__ = "1.4" +__version__ = "1.5" __author__ = 'Vocard Development, ChocoMeow' __license__ = "MIT" __copyright__ = "Copyright 2023 - present (c) Vocard Development, ChocoMeow" +from .config import Config from .enums import SearchType, LoopType from .events import * from .exceptions import * from .filters import * from .objects import * -from .player import Player, connect_channel from .pool import * from .queue import * -from .placeholders import Placeholders, build_embed -from .transformer import encode, decode +from .player import Player, connect_channel +from .placeholders import PlayerPlaceholder, BotPlaceholder +from .mongodb import MongoDBHandler +from .language import LangHandler +from .lyrics import LYRICS_PLATFORMS +from .ipc import IPCClient diff --git a/voicelink/config.py b/voicelink/config.py new file mode 100644 index 0000000..55e3c1a --- /dev/null +++ b/voicelink/config.py @@ -0,0 +1,137 @@ +"""MIT License + +Copyright (c) 2023 - present Vocard Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import os + +from pathlib import Path +from dotenv import load_dotenv +from typing import ( + Dict, + List, + Any, + Union, + Optional +) + +from .enums import SearchType + +load_dotenv() + +class Config: + _instance: Optional['Config'] = None + WORKING_DIR: Path = Path(__file__).resolve().parent.parent + LAST_SESSION_FILE_DIR: str = WORKING_DIR / "last-session.json" + + def __new__(cls, settings: Dict[str, Any] = None) -> 'Config': + """ + Singleton pattern to ensure only one instance of Config exists. + If settings are provided, creates a new instance that replaces the old one. + + Args: + settings (Dict[str, Any], optional): A dictionary containing configuration settings. Defaults to None. + If provided, creates a new instance that replaces the old one. + """ + if settings is not None: + instance = super(Config, cls).__new__(cls) + instance.__init__(settings) + cls._instance = instance + return instance + + if cls._instance is None: + cls._instance = super(Config, cls).__new__(cls) + + return cls._instance + + def __init__(self, settings: Dict[str, Any] = None) -> None: + """ + Initialize configuration settings. + + Args: + settings (Dict[str, Any], optional): A dictionary containing configuration settings. + If None, uses empty dict with default values. + """ + if hasattr(self, 'initialized'): + return + + settings = settings or {} + + self.token: str = settings.get("token") or os.getenv("TOKEN") + self.client_id: int = int(settings.get("client_id", 0)) or int(os.getenv("CLIENT_ID")) + self.genius_token: str = settings.get("genius_token") or os.getenv("GENIUS_TOKEN") + self.mongodb_url: str = settings.get("mongodb_url") or os.getenv("MONGODB_URL") + self.mongodb_name: str = settings.get("mongodb_name") or os.getenv("MONGODB_NAME") + + self.invite_link: str = "https://discord.gg/wRCgB7vBQv" + self.nodes: Dict[str, Dict[str, Union[str, int, bool]]] = settings.get("nodes", {}) + self.max_queue: int = settings.get("default_max_queue", 1000) + self.search_platform: SearchType = SearchType.match(settings.get("default_search_platform", "youtube")) or SearchType.YOUTUBE + self.bot_prefix: str = settings.get("prefix", "") + self.activity: List[Dict[str, str]] = settings.get("activity", [{"listen": "/help"}]) + self.logging: Dict[Union[str, Dict[str, Union[str, bool]]]] = settings.get("logging", {}) + self.embed_color: str = int(settings.get("embed_color", "0xb3b3b3"), 16) + self.bot_access_user: List[int] = settings.get("bot_access_user", []) + self.sources_settings: Dict[Dict[str, str]] = settings.get("sources_settings", {}) + self.cooldowns_settings: Dict[str, List[int]] = settings.get("cooldowns", {}) + self.aliases_settings: Dict[str, List[str]] = settings.get("aliases", {}) + self.controller: Dict[str, Dict[str, Any]] = settings.get("default_controller", {}) + self.voice_status_template: str = settings.get("default_voice_status_template", "") + self.lyrics_platform: str = settings.get("lyrics_platform", "A_ZLyrics").lower() + self.ipc_client: Dict[str, Union[str, bool, int]] = settings.get("ipc_client", {}) + self.playlist_settings: Dict[str, Union[str, int]] = settings.get("playlist_settings", {}) + self.version: str = settings.get("version", "") + + self.initialized = True + + @classmethod + def get_source_config(cls, source: str, type: str) -> Union[str, None]: + """ + Get source configuration for a specific source and type. + + Args: + source (str): The source identifier (e.g., 'youtube', 'spotify'). + Case-insensitive and spaces are removed. + type (str): The type of configuration to retrieve (e.g., 'emoji', 'color'). + + Returns: + Union[str, None]: The configuration value for the specified source and type. + Returns None if either the source or type doesn't exist. + + Example: + >>> Config().get_source("youtube", "emoji") + "๐ŸŽต" + """ + if not isinstance(source, str) or not isinstance(type, str): + return None + + normalized_source: str = source.lower().strip().replace(" ", "") + source_settings: dict[str, str] = cls._instance.sources_settings.get( + normalized_source, + cls._instance.sources_settings.get("others", {}) + ) + + return source_settings.get(type) + + @classmethod + def get_playlist_config(cls) -> tuple[int, int, str]: + config = cls._instance.playlist_settings + return config.get("max_playlists", 5), config.get("max_tracks_per_playlist", 500), config.get("default_playlist_name", "Favourite") \ No newline at end of file diff --git a/voicelink/enums.py b/voicelink/enums.py index ef74c58..bc92915 100644 --- a/voicelink/enums.py +++ b/voicelink/enums.py @@ -37,22 +37,43 @@ class LoopType(Enum): QUEUE = auto() class SearchType(Enum): - """The enum for the different search types for Voicelink. + """Enum representing different search types for Voicelink. - SearchType.YOUTUBE searches using regular Youtube, - which is best for all scenarios. + Each search type corresponds to a specific platform that can be used + for retrieving audio or video content. The options available are: - SearchType.YOUTUBE_MUSIC searches using YouTube Music, - which is best for getting audio-only results. - - SearchType.SPOTIFY searches using Spotify, - which is an alternative to YouTube or YouTube Music. - - SearchType.SOUNDCLOUD searches using SoundCloud, - which is an alternative to YouTube or YouTube Music. - - SearchType.APPLE_MUSIC searches using Apple Music, - which is an alternative to YouTube or YouTube Music. + - SearchType.YOUTUBE: + Searches using regular YouTube, ideal for all scenarios. + + - SearchType.YOUTUBE_MUSIC: + Searches using YouTube Music, best for obtaining audio-only results. + + - SearchType.SPOTIFY: + Searches using Spotify, a viable alternative to YouTube and YouTube Music. + + - SearchType.SOUNDCLOUD: + Searches using SoundCloud, another alternative to YouTube and YouTube Music. + + - SearchType.APPLE_MUSIC: + Searches using Apple Music, offering a different option for audio content. + + - SearchType.DEEZER: + Searches using Deezer, providing another music streaming alternative. + + - SearchType.YANDEX_MUSIC: + Searches using Yandex Music, catering to users in the Yandex ecosystem. + + - SearchType.VK_MUSIC: + Searches using VK Music, popular in certain regions for music streaming. + + - SearchType.TIDAL: + Searches using Tidal, known for high-fidelity music streaming. + + - SearchType.QOBUZ: + Searches using Qobuz, recognized for high-resolution audio. + + - SearchType.JIOSAAVN: + Searches using JioSaavn, a popular platform in India for music streaming. """ YOUTUBE = "ytsearch" @@ -60,6 +81,12 @@ class SearchType(Enum): SPOTIFY = "spsearch" SOUNDCLOUD = "scsearch" APPLE_MUSIC = "amsearch" + DEEZER = "dzsearch" + YANDEX_MUSIC = "ymsearch" + VK_MUSIC = "vksearch" + TIDAL = "tdsearch" + QOBUZ = "qbsearch" + JIOSAAVN = "jssearch" def __str__(self) -> str: return self.value diff --git a/ipc/__init__.py b/voicelink/ipc/__init__.py similarity index 100% rename from ipc/__init__.py rename to voicelink/ipc/__init__.py diff --git a/ipc/client.py b/voicelink/ipc/client.py similarity index 97% rename from ipc/client.py rename to voicelink/ipc/client.py index de0c460..cf1936d 100644 --- a/ipc/client.py +++ b/voicelink/ipc/client.py @@ -1,7 +1,7 @@ import aiohttp import asyncio import logging -import function as func +import voicelink from discord.ext import commands from typing import Optional @@ -29,7 +29,7 @@ class IPCClient: self._is_secure: bool = secure self._is_connected: bool = False self._is_connecting: bool = False - self._logger: logging.Logger = logging.getLogger("ipc_client") + self._logger: logging.Logger = logging.getLogger("vocard.ipc_client") self._websocket_url: str = f"{'wss' if self._is_secure else 'ws'}://{self._host}:{self._port}/ws_bot" self._session: Optional[aiohttp.ClientSession] = None @@ -39,7 +39,7 @@ class IPCClient: self._heanders = { "Authorization": self._password, "User-Id": str(bot.user.id), - "Client-Version": func.settings.version + "Client-Version": voicelink.Config().version } async def _listen(self) -> None: diff --git a/ipc/methods.py b/voicelink/ipc/methods.py similarity index 87% rename from ipc/methods.py rename to voicelink/ipc/methods.py index 9a93a17..146197c 100644 --- a/ipc/methods.py +++ b/voicelink/ipc/methods.py @@ -1,13 +1,17 @@ +from __future__ import annotations + import time, re -import function as func -from typing import List, Dict, Union, Optional +from typing import List, Dict, Union, Optional, TYPE_CHECKING -from discord import User, Member, VoiceChannel +from discord import User, Member from discord.ext import commands -from voicelink import Player, Track, Playlist, NodePool, decode, LoopType, Filters -from addons import LYRICS_PLATFORMS +from voicelink import Player, Track, Playlist, NodePool, LoopType, Filters, Config, MongoDBHandler, LangHandler, LYRICS_PLATFORMS +from voicelink.utils import TempCtx +if TYPE_CHECKING: + from .client import IPCClient + RATELIMIT_COUNTER: Dict[int, Dict[str, float]] = {} SCOPES = { "prefix": str, @@ -62,8 +66,8 @@ async def connect_channel(member: Member, bot: commands.Bot) -> Player: channel = member.voice.channel try: - settings = await func.get_settings(channel.guild.id) - player: Player = await channel.connect(cls=Player(bot, channel, func.TempCtx(member, channel), settings)) + settings = await MongoDBHandler.get_settings(channel.guild.id) + player: Player = await channel.connect(cls=Player(bot, channel, TempCtx(member, channel), settings)) await player.send_ws({"op": "createPlayer", "memberIds": [str(member.id) for member in channel.members]}) return player except: @@ -86,7 +90,7 @@ async def initBot(bot: commands.Bot, data: Dict) -> Dict: async def initUser(bot: commands.Bot, data: Dict) -> Dict: user_id = int(data.get("userId")) - data = await func.get_user(user_id) + data = await MongoDBHandler.get_user(user_id) for mail in data.get("inbox"): sender = bot.get_user(mail.get("sender")) @@ -146,7 +150,7 @@ async def getRecommendation(bot: commands.Bot, data: Dict) -> None: if not node: return - track_data = decode(track_id := data.get("trackId")) + track_data = Track.decode(track_id := data.get("trackId")) track = Track(track_id=track_id, info=track_data, requester=bot.user) tracks: List[Track] = await node.get_recommendations(track, limit=60) @@ -163,12 +167,12 @@ async def skipTo(player: Player, member: Member, data: Dict) -> None: pass elif member in player.skip_votes: - return error_msg(player.get_msg('voted'), user_id=member.id) + return error_msg(player.get_msg('voting.voted'), user_id=member.id) else: player.skip_votes.add(member) if len(player.skip_votes) < (required := player.required()): - return error_msg(player.get_msg('skipVote').format(member, len(player.skip_votes), required), guild_id=player.guild.id) + return error_msg(player.get_msg('player.controls.skip.vote').format(member, len(player.skip_votes), required), guild_id=player.guild.id) index = data.get("index", 1) if index > 1: @@ -184,12 +188,12 @@ async def backTo(player: Player, member: Member, data: Dict) -> None: pass elif member in player.skip_votes: - return error_msg(player.get_msg('voted'), user_id=member.id) + return error_msg(player.get_msg('voting.voted'), user_id=member.id) else: player.skip_votes.add(member) if len(player.skip_votes) < (required := player.required()): - return error_msg(player.get_msg('backVote').format(member, len(player.skip_votes), required), guild_id=player.guild.id) + return error_msg(player.get_msg('player.controls.back.vote').format(member, len(player.skip_votes), required), guild_id=player.guild.id) index = data.get("index", 1) if not player.is_playing: @@ -212,7 +216,7 @@ async def addTracks(player: Player, member: Member, data: Dict) -> None: _type = data.get("type", "addToQueue") tracks = [Track( track_id=track_id, - info=decode(track_id), + info=Track.decode(track_id), requester=member ) for track_id in data.get("tracks", [])] @@ -249,11 +253,11 @@ async def searchAndPlay(player: Player, member: Member, data: Dict) -> None: async def shuffleTrack(player: Player, member: Member, data: Dict) -> None: if not player.is_privileged(member): if member in player.shuffle_votes: - return error_msg(player.get_msg('voted'), user_id=member.id) + return error_msg(player.get_msg('voting.voted'), user_id=member.id) player.shuffle_votes.add(member) if len(player.shuffle_votes) < (required := player.required()): - return error_msg(player.get_msg('shuffleVote').format(member, len(player.skip_votes), required), guild_id=player.guild.id) + return error_msg(player.get_msg('player.controls.shuffle.vote').format(member, len(player.skip_votes), required), guild_id=player.guild.id) await player.shuffle(data.get("type", "queue"), member) @@ -274,7 +278,7 @@ async def clearQueue(player: Player, member: Member, data: Dict) -> None: @require_permission(only_admin=True) async def updateVolume(player: Player, member: Member, data: Dict) -> None: volume = data.get("volume", 100) - await func.update_settings(player.guild.id, {"$set": {"volume": volume}}) + await MongoDBHandler.update_settings(player.guild.id, {"$set": {"volume": volume}}) await player.set_volume(volume=volume, requester=member) async def updatePause(player: Player, member: Member, data: Dict) -> None: @@ -282,19 +286,19 @@ async def updatePause(player: Player, member: Member, data: Dict) -> None: if not player.is_privileged(member): if pause: if member in player.pause_votes: - return error_msg(player.get_msg('voted'), user_id=member.id) + return error_msg(player.get_msg('voting.voted'), user_id=member.id) player.pause_votes.add(member) if len(player.pause_votes) < (required := player.required()): - return error_msg(player.get_msg('pauseVote').format(member, len(player.pause_votes), required), guild_id=player.guild.id) + return error_msg(player.get_msg('player.controls.pause.vote').format(member, len(player.pause_votes), required), guild_id=player.guild.id) else: if member in player.resume_votes: - return error_msg(player.get_msg('voted'), user_id=member.id) + return error_msg(player.get_msg('voting.voted'), user_id=member.id) player.resume_votes.add(member) if len(player.resume_votes) < (required := player.required()): - return error_msg(player.get_msg('resumeVote').format(member, len(player.resume_votes), required), guild_id=player.guild.id) + return error_msg(player.get_msg('player.controls.resume.vote').format(member, len(player.resume_votes), required), guild_id=player.guild.id) await player.set_pause(pause, member) @@ -305,7 +309,7 @@ async def updatePosition(player: Player, member: Member, data: Dict) -> None: async def toggleAutoplay(player: Player, member: Member, data: Dict) -> Dict: if not player.is_privileged(member): - return error_msg(player.get_msg('missingAutoPlayPerm')) + return error_msg(player.get_msg('permissions.missingAutoPlay')) check = data.get("status", False) player.settings['autoplay'] = check @@ -358,13 +362,13 @@ def _assign_playlist_id(existed: list) -> str: return str(i) async def _getPlaylist(user_id: int, playlist_id: str) -> Dict: - playlists = await func.get_user(user_id, "playlist") + playlists = await MongoDBHandler.get_user(user_id, d_type="playlist") playlist = playlists.get(playlist_id) if not playlist: return if playlist["type"] == "share": - target_user = await func.get_user(playlist["user"], "playlist") + target_user = await MongoDBHandler.get_user(playlist["user"], d_type="playlist") target_playlist = target_user.get(playlist["referId"]) if target_playlist and user_id in target_playlist.get("perms", {}).get("read", []): playlist["tracks"] = await _loadPlaylist(target_playlist) @@ -391,7 +395,7 @@ async def updatePlaylist(bot: commands.Bot, data: Dict) -> Dict: if not playlist_id and not _type == "createPlaylist": return error_msg("Unable to process this request without a playlist ID.", user_id=user_id, level="error") - rank, max_p, max_t = func.check_roles() + max_p, max_t, _ = Config().get_playlist_config() if _type == "createPlaylist": name, playlist_url = data.get("playlistName"), data.get("playlistUrl") if not name: @@ -403,7 +407,7 @@ async def updatePlaylist(bot: commands.Bot, data: Dict) -> Dict: "userId": str(user_id) } - playlist = await func.get_user(user_id, "playlist") + playlist = await MongoDBHandler.get_user(user_id, d_type="playlist") if len(list(playlist.keys())) >= max_p: return { "op": "updatePlaylist", @@ -436,7 +440,7 @@ async def updatePlaylist(bot: commands.Bot, data: Dict) -> Dict: assigned_playlist_id = _assign_playlist_id(list(playlist.keys())) data = {'uri': playlist_url, 'perms': {'read': []}, 'name': name, 'type': 'link'} if playlist_url else {'tracks': [], 'perms': {'read': [], 'write': [], 'remove': []}, 'name': name, 'type': 'playlist'} - await func.update_user(user_id, {"$set": {f"playlist.{assigned_playlist_id}": data}}) + await MongoDBHandler.update_user(user_id, {"$set": {f"playlist.{assigned_playlist_id}": data}}) return { "op": "updatePlaylist", "status": "created", @@ -450,9 +454,9 @@ async def updatePlaylist(bot: commands.Bot, data: Dict) -> Dict: playlist = await _getPlaylist(user_id, playlist_id) if playlist: if playlist['type'] == 'share': - await func.update_user(playlist['user'], {"$pull": {f"playlist.{playlist['referId']}.perms.read": user_id}}) + await MongoDBHandler.update_user(playlist['user'], {"$pull": {f"playlist.{playlist['referId']}.perms.read": user_id}}) - await func.update_user(user_id, {"$unset": {f"playlist.{playlist_id}": 1}}) + await MongoDBHandler.update_user(user_id, {"$unset": {f"playlist.{playlist_id}": 1}}) return { "op": "updatePlaylist", @@ -473,7 +477,7 @@ async def updatePlaylist(bot: commands.Bot, data: Dict) -> Dict: "userId": str(user_id) } - playlist = await func.get_user(user_id, "playlist") + playlist = await MongoDBHandler.get_user(user_id, d_type="playlist") for data in playlist.values(): if data['name'].lower() == name.lower(): return { @@ -484,7 +488,7 @@ async def updatePlaylist(bot: commands.Bot, data: Dict) -> Dict: "userId": str(user_id) } - await func.update_user(user_id, {"$set": {f'playlist.{playlist_id}.name': name}}) + await MongoDBHandler.update_user(user_id, {"$set": {f'playlist.{playlist_id}.name': name}}) return { "op": "updatePlaylist", "status": "renamed", @@ -504,15 +508,15 @@ async def updatePlaylist(bot: commands.Bot, data: Dict) -> Dict: if playlist['type'] in ['share', 'link']: return error_msg("You cannot add songs to a linked playlist through Vocard.", user_id=user_id, level='error') - rank, max_p, max_t = func.check_roles() + max_p, max_t, _ = Config().get_playlist_config() if len(playlist['tracks']) >= max_t: return error_msg(f"You have reached the limit! You can only add {max_t} songs to your playlist.", user_id=user_id) - decoded_track = Track(track_id=track_id, info=decode(track_id), requester=None) + decoded_track = Track(track_id=track_id, info=Track.decode(track_id), requester=None) if decoded_track.is_stream: return error_msg("You are not allowed to add streaming videos to your playlist.", user_id=user_id) - await func.update_user(user_id, {"$push": {f'playlist.{playlist_id}.tracks': track_id}}) + await MongoDBHandler.update_user(user_id, {"$push": {f'playlist.{playlist_id}.tracks': track_id}}) return { "op": "updatePlaylist", "status": "addTrack", @@ -540,9 +544,9 @@ async def updatePlaylist(bot: commands.Bot, data: Dict) -> Dict: if playlist['tracks'][track_position] != track_id: return error_msg("Something wrong while removing the track from your playlist.", user_id=user_id, level='error') - await func.update_user(user_id, {"$pull": {f'playlist.{playlist_id}.tracks': playlist['tracks'][track_position]}}) + await MongoDBHandler.update_user(user_id, {"$pull": {f'playlist.{playlist_id}.tracks': playlist['tracks'][track_position]}}) - decoded_track = decode(playlist['tracks'][track_position]) + decoded_track = Track.decode(playlist['tracks'][track_position]) return { "op": "updatePlaylist", "status": "removeTrack", @@ -554,7 +558,7 @@ async def updatePlaylist(bot: commands.Bot, data: Dict) -> Dict: } elif _type == "updateInbox": - user = await func.get_user(user_id) + user = await MongoDBHandler.get_user(user_id) is_accept = data.get("accept", False) if is_accept and len(list(user.get("playlist").keys())) >= max_p: @@ -571,7 +575,7 @@ async def updatePlaylist(bot: commands.Bot, data: Dict) -> Dict: del inbox[index] if is_accept: - share_playlists = await func.get_user(mail["sender"], "playlist") + share_playlists = await MongoDBHandler.get_user(mail["sender"], d_type="playlist") if refer_id not in share_playlists: return error_msg("The shared playlist couldnโ€™t be found. Itโ€™s possible that the user has already deleted it.", user_id=user_id) @@ -582,8 +586,8 @@ async def updatePlaylist(bot: commands.Bot, data: Dict) -> Dict: "name": playlist_name, "type": "share" }) - await func.update_user(mail['sender'], {"$push": {f"playlist.{mail['referId']}.perms.read": user_id}}) - await func.update_user(user_id, {"$set": { + await MongoDBHandler.update_user(mail['sender'], {"$push": {f"playlist.{mail['referId']}.perms.read": user_id}}) + await MongoDBHandler.update_user(user_id, {"$set": { f'playlist.{assigned_playlist_id}': { 'user': mail['sender'], 'referId': mail['referId'], 'name': playlist_name, @@ -598,7 +602,7 @@ async def updatePlaylist(bot: commands.Bot, data: Dict) -> Dict: "data": share_playlist, }) - await func.update_user(user_id, {"$set": {"inbox": inbox}}) + await MongoDBHandler.update_user(user_id, {"$set": {"inbox": inbox}}) return payload async def getMutualGuilds(bot: commands.Bot, data: Dict) -> Dict: @@ -631,7 +635,7 @@ async def getSettings(bot: commands.Bot, data: Dict) -> Dict: if not member.guild_permissions.manage_guild: return error_msg("You don't have permission to access the settings.", user_id=user_id, level='error') - settings = await func.get_settings(guild_id) + settings = await MongoDBHandler.get_settings(guild_id) if "dj" in settings: role = guild.get_role(settings["dj"]) if role: @@ -641,7 +645,7 @@ async def getSettings(bot: commands.Bot, data: Dict) -> Dict: "op": "getSettings", "settings": settings, "options": { - "languages": list(func.LANGS.keys()), + "languages": list(LangHandler.get_all_languages()), "queueModes": ["Queue", "FairQueue"], "roles": [role.name for role in guild.roles] }, @@ -656,7 +660,7 @@ async def getSettings(bot: commands.Bot, data: Dict) -> Dict: async def getLyrics(bot: commands.Bot, data: Dict) -> Dict: title, artist, platform = data.get("title", ""), data.get("artist", ""), data.get("platform", "") if not platform or platform not in LYRICS_PLATFORMS: - platform = func.settings.lyrics_platform + platform = Config().lyrics_platform lyrics_platform = LYRICS_PLATFORMS.get(platform) if lyrics_platform: @@ -697,7 +701,7 @@ async def updateSettings(bot: commands.Bot, data: Dict) -> None: if key not in SCOPES or not isinstance(value, SCOPES[key]): del data[key] - await func.update_settings(guild.id, {"$set": data}) + await MongoDBHandler.update_settings(guild.id, {"$set": data}) METHODS: Dict[str, Union[SystemMethod, PlayerMethod]] = { "initBot": SystemMethod(initBot, credit=0), @@ -728,7 +732,7 @@ METHODS: Dict[str, Union[SystemMethod, PlayerMethod]] = { "searchAndPlay": PlayerMethod(searchAndPlay, credit=5, auto_connect=True) } -async def process_methods(ipc_client, bot: commands.Bot, data: Dict) -> None: +async def process_methods(ipc_client: IPCClient, bot: commands.Bot, data: Dict) -> None: op: str = data.get("op", "") method = METHODS.get(op) if not method or not (user_id := data.get("userId")): diff --git a/voicelink/language.py b/voicelink/language.py new file mode 100644 index 0000000..deb8029 --- /dev/null +++ b/voicelink/language.py @@ -0,0 +1,158 @@ +"""MIT License + +Copyright (c) 2023 - present Vocard Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import os +import json +import logging + +from typing import Optional, Union, List, Dict, Any + +from .config import Config +from .mongodb import MongoDBHandler + +logger = logging.getLogger("vocard.language") + +class LangHandler: + """ + Static class for handling language and localization strings. + Preloads all language files at initialization for fast access. + """ + + _langs: dict[str, dict[str, str]] = {} + _local_langs: dict[str, dict[str, str]] = {} + + @classmethod + def init( + cls, + langs_dir: str = Config.WORKING_DIR / "langs", + local_langs_dir: str = Config.WORKING_DIR / "local_langs" + ) -> "LangHandler": + """ + Initialize the language handler by preloading all language files + from the given directories into memory. + + Args: + langs_dir (str): Directory containing main language JSON files. + local_langs_dir (str): Directory containing localization JSON files. + + Returns: + LangHandler: The class itself with loaded languages. + """ + targets = [ + (langs_dir, cls._langs, "language"), + (local_langs_dir, cls._local_langs, "local language"), + ] + + for directory, storage, label in targets: + if not os.path.exists(directory): + logger.warning(f"{label.capitalize()} directory '{directory}' does not exist.") + continue + + for file in os.listdir(directory): + if file.endswith(".json"): + lang_code = file.split(".")[0].upper() + filepath = os.path.join(directory, file) + try: + with open(filepath, encoding="utf8") as f: + storage[lang_code] = cls._flatten_json(json.load(f)) + logger.info(f"Loaded {label}: {lang_code}") + except Exception as e: + logger.error(f"Failed to load {label} file '{filepath}': {e}") + + return cls + + @classmethod + def _flatten_json(cls, nested_json: Dict[str, Any], parent_key: str = '', separator: str = '.') -> Dict[str, str]: + """ + Flatten a nested JSON object into a single-level dictionary with dot-separated keys. + + Args: + nested_json (dict): The nested JSON object to flatten + parent_key (str): The base key for recursion (used internally) + separator (str): The separator to use between keys (default: '.') + + Returns: + dict: Flattened dictionary with dot-separated keys + """ + flattened = {} + + for key, value in nested_json.items(): + new_key = f"{parent_key}{separator}{key}" if parent_key else key + + if isinstance(value, dict): + flattened.update(cls._flatten_json(value, new_key, separator)) + else: + flattened[new_key] = value + + return flattened + + @classmethod + def _get_lang(cls, lang: str, *keys) -> Optional[Union[list[str], str]]: + """ + Internal helper to retrieve strings from the preloaded cls._lang cache. + + Args: + lang (str): Language code (e.g., "EN", "FR"). + *keys: One or more keys to fetch from the language dictionary. + + Returns: + str | list[str] | None: The requested string(s), or "Not found!" if missing. + """ + lang = lang.upper() + if lang not in cls._langs: + lang = "EN" + + lang_dict = cls._langs.get(lang, {}) + if len(keys) == 1: + value = lang_dict.get(keys[0], "Not found!") + return value + + values = [lang_dict.get(key, "Not found!") for key in keys] + return values + + @classmethod + async def get_lang(cls, guild_id: int, *keys) -> Optional[Union[list[str], str]]: + """ + Fetch the language setting for a guild from the database and return + the requested string(s). + + Args: + guild_id (int): Guild ID to fetch settings for. + *keys: One or more keys to fetch from the language dictionary. + + Returns: + str | list[str] | None: The requested string(s). + """ + settings = await MongoDBHandler.get_settings(guild_id) + lang = settings.get("lang", "EN") + return cls._get_lang(lang, *keys) + + @classmethod + def get_all_languages(cls) -> List[str]: + """ + Get a combined list of all loaded language codes (main + local). + + Returns: + list[str]: List of all available language codes. + """ + return cls._langs.keys() \ No newline at end of file diff --git a/addons/lyrics.py b/voicelink/lyrics.py similarity index 99% rename from addons/lyrics.py rename to voicelink/lyrics.py index e05168a..45775ce 100644 --- a/addons/lyrics.py +++ b/voicelink/lyrics.py @@ -29,15 +29,16 @@ import hashlib import base64 import json import urllib.parse -import function as func -from datetime import datetime -from abc import ABC, abstractmethod -from urllib.parse import quote from math import floor +from urllib.parse import quote +from abc import ABC, abstractmethod from importlib import import_module +from datetime import datetime from typing import Optional, Type +from .config import Config + userAgents = '''Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.11 Safari/535.19 @@ -202,7 +203,7 @@ class A_ZLyrics(LyricsPlatform): class Genius(LyricsPlatform): def __init__(self) -> None: self.module = import_module("lyricsgenius") - self.genius = self.module.Genius(func.settings.genius_token) + self.genius = self.module.Genius(Config().genius_token) async def get_lyrics(self, title: str, artist: str) -> Optional[dict[str, str]]: song = self.genius.search_song(title=title, artist=artist) diff --git a/voicelink/mongodb.py b/voicelink/mongodb.py new file mode 100644 index 0000000..8bb8eb9 --- /dev/null +++ b/voicelink/mongodb.py @@ -0,0 +1,537 @@ +"""MIT License + +Copyright (c) 2023 - present Vocard Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import copy +import time +import asyncio +import logging + +from typing import Any, Dict, Optional, Literal, TypedDict, List +from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorCollection + +logger: logging.Logger = logging.getLogger("vocard.db") + +# Type definitions for better code clarity +class PlaylistPerms(TypedDict): + read: List[int] + write: List[int] + remove: List[int] + +class Playlist(TypedDict): + tracks: List[Dict[str, Any]] + perms: PlaylistPerms + name: str + type: Literal["playlist"] + +class UserData(TypedDict): + _id: int + playlist: Dict[str, Playlist] + history: List[Dict[str, Any]] + inbox: List[Dict[str, Any]] + +UpdateOperationType = Literal["$set", "$unset", "$inc", "$push", "$pull"] + + +class MongoDBHandler: + """ + Handles MongoDB operations with connection pooling and caching. + Implements a thread-safe singleton pattern for database connections. + """ + + # Static instance variables + _client: Optional[AsyncIOMotorClient] = None + _db: Optional[Any] = None + _settings_db: Optional[AsyncIOMotorCollection] = None + _users_db: Optional[AsyncIOMotorCollection] = None + _lock: asyncio.Lock = asyncio.Lock() + + # Cache with TTL (Time To Live in seconds) + _CACHE_TTL: int = 300 # 5 minutes + _settings_buffer: Dict[int, Dict[str, Any]] = {} + _users_buffer: Dict[int, Dict[str, Any]] = {} + _last_access: Dict[int, float] = {} # Tracks last access time for cache entries + + # Maximum cache size to prevent memory issues + _MAX_CACHE_SIZE: int = 10000 + + # Default user template + _user_base: UserData = { + "_id": 0, # Will be replaced with actual user ID + "playlist": { + "200": { + "tracks": [], + "perms": {"read": [], "write": [], "remove": []}, + "name": "Favourite", + "type": "playlist", + } + }, + "history": [], + "inbox": [], + } + + @classmethod + async def init(cls, uri: str, db_name: str) -> None: + """ + Initialize the MongoDB connection with connection pooling and error handling. + + Args: + uri (str): MongoDB connection URI + db_name (str): Name of the database to use + + Raises: + ConnectionError: If unable to connect to MongoDB + Exception: For other initialization errors + """ + if not uri or not db_name: + logger.error("MongoDB initialization failed: URI or database name is missing.") + raise ValueError("Both URI and database name must be provided.") + + async with cls._lock: + if cls._client is not None: + logger.warning("MongoDB client is already initialized. Skipping reinitialization.") + return + + logger.debug("Initializing MongoDB client with URI: %s and DB name: %s", uri, db_name) + + try: + cls._client = AsyncIOMotorClient( + uri, + maxPoolSize=50, + minPoolSize=5, + maxIdleTimeMS=60000, + retryWrites=True + ) + logger.debug("MongoDB client created successfully. Testing connection...") + + await cls._client.server_info() + logger.debug("MongoDB connection test passed.") + + cls._db = cls._client[db_name] + cls._settings_db = cls._db["Settings"] + cls._users_db = cls._db["Users"] + + logger.info("MongoDB databases initialized: %s", db_name) + + except Exception as e: + logger.error("MongoDB initialization failed: %s", str(e), exc_info=True) + + cls._client = None + cls._db = None + cls._settings_db = None + cls._users_db = None + + raise ConnectionError(f"Failed to initialize MongoDB: {str(e)}") + + @classmethod + async def cleanup_cache(cls) -> None: + """ + Cleanup expired cache entries to prevent memory leaks. + Should be called periodically or when cache size exceeds _MAX_CACHE_SIZE. + """ + current_time = time.time() + logger.info("Starting cache cleanup at timestamp: %.2f", current_time) + + async with cls._lock: + try: + # Remove expired entries from settings cache + expired_settings = [ + guild_id for guild_id, last_access in cls._last_access.items() + if current_time - last_access > cls._CACHE_TTL and guild_id in cls._settings_buffer + ] + logger.debug("Found %d expired cache entries.", len(expired_settings)) + + for guild_id in expired_settings: + del cls._settings_buffer[guild_id] + del cls._last_access[guild_id] + logger.debug("Removed expired cache for guild_id: %s", guild_id) + + # If still too large, remove oldest entries + while len(cls._settings_buffer) > cls._MAX_CACHE_SIZE: + oldest_id = min(cls._last_access.items(), key=lambda x: x[1])[0] + del cls._settings_buffer[oldest_id] + del cls._last_access[oldest_id] + logger.warning("Cache size exceeded. Removed oldest entry: %s", oldest_id) + + logger.info("Cache cleanup completed. Current cache size: %d", len(cls._settings_buffer)) + + except Exception as e: + logger.error("Cache cleanup failed: %s", str(e), exc_info=True) + + @classmethod + async def _update_db( + cls, + db: AsyncIOMotorCollection, + cache: Dict[str, Any], + filter_: Dict[str, Any], + data: Dict[UpdateOperationType, Dict[str, Any]], + ) -> bool: + """ + Update database and cache atomically with error handling and validation. + + Args: + db: MongoDB collection to update + cache: Cache dictionary to update + filter_: MongoDB filter for the update + data: Update operations to perform + + Returns: + bool: True if update was successful, False otherwise + + Raises: + ValueError: If invalid update operation is provided + """ + async with cls._lock: + try: + # Validate update operations + valid_operations = {"$set", "$unset", "$inc", "$push", "$pull"} + if not all(op in valid_operations for op in data.keys()): + raise ValueError(f"Invalid update operation. Must be one of {valid_operations}") + + # Update cache first + for mode, action in data.items(): + for key, value in action.items(): + cursors = key.split(".") + nested = cache + + # Ensure path exists + for c in cursors[:-1]: + if not isinstance(nested, dict): + raise ValueError(f"Invalid path: {key}") + nested = nested.setdefault(c, {}) + + field = cursors[-1] + + try: + if mode == "$set": + nested[field] = value + elif mode == "$unset": + nested.pop(field, None) + elif mode == "$inc": + if not isinstance(nested.get(field, 0), (int, float)): + raise ValueError(f"Cannot increment non-numeric field: {field}") + nested[field] = nested.get(field, 0) + value + elif mode == "$push": + arr = nested.setdefault(field, []) + if not isinstance(arr, list): + raise ValueError(f"Cannot push to non-array field: {field}") + if isinstance(value, dict) and "$each" in value: + arr.extend(value["$each"]) + if "$slice" in value: + arr[:] = arr[value["$slice"]:] + else: + arr.append(value) + elif mode == "$pull": + if field in nested: + if not isinstance(nested[field], list): + raise ValueError(f"Cannot pull from non-array field: {field}") + values = value.get("$in", []) if isinstance(value, dict) else [value] + nested[field] = [item for item in nested[field] if item not in values] + except Exception as e: + raise ValueError(f"Error updating {key}: {str(e)}") + + # Then update database + result = await db.update_one(filter_, data) + + # Update last access time + if '_id' in filter_: + cls._last_access[filter_['_id']] = time.time() + + return result.modified_count > 0 + + except Exception as e: + # Rollback cache if database update fails + if '_id' in filter_: + cls._settings_buffer.pop(filter_['_id'], None) + cls._users_buffer.pop(filter_['_id'], None) + raise Exception(f"Update failed: {str(e)}") + + @classmethod + def get_cached_settings( + cls, + guild_id: int + ) -> Dict[str, Any]: + """ + Retrieve settings for a guild with caching. + + Args: + guild_id: The Discord guild ID + + Returns: + Dict containing guild settings or empty dict if not found + """ + try: + if guild_id not in cls._settings_buffer: + return {} + + return copy.deepcopy(cls._settings_buffer[guild_id]) + + except Exception as e: + raise ConnectionError(f"Failed to retrieve settings: {str(e)}") + + @classmethod + async def get_settings( + cls, + guild_id: int, + *, + force_refresh: bool = False + ) -> Dict[str, Any]: + """ + Retrieve settings for a guild with caching. + + Args: + guild_id: The Discord guild ID + force_refresh: If True, bypass cache and fetch fresh data + + Returns: + Dict containing guild settings + + Raises: + ConnectionError: If database operation fails + """ + try: + async with cls._lock: + # Check if we need fresh data + if force_refresh or guild_id not in cls._settings_buffer: + settings = await cls._settings_db.find_one({"_id": guild_id}) + if not settings: + settings = {"_id": guild_id} + try: + await cls._settings_db.insert_one(settings) + except Exception as e: + raise ConnectionError(f"Failed to create settings: {str(e)}") + + cls._settings_buffer[guild_id] = settings + cls._last_access[guild_id] = time.time() + + return copy.deepcopy(cls._settings_buffer[guild_id]) + + except Exception as e: + raise ConnectionError(f"Failed to retrieve settings: {str(e)}") + + @classmethod + async def update_settings( + cls, + guild_id: int, + data: Dict[UpdateOperationType, Dict[str, Any]], + *, + upsert: bool = False + ) -> bool: + """ + Update settings for a guild. + + Args: + guild_id: The Discord guild ID + data: Update operations to perform + upsert: If True, create document if it doesn't exist + + Returns: + bool: True if update was successful + + Raises: + ValueError: If invalid update data is provided + ConnectionError: If database operation fails + """ + try: + settings = await cls.get_settings(guild_id) + result = await cls._update_db( + cls._settings_db, + settings, + {"_id": guild_id}, + data + ) + + if not result and upsert: + # Try to insert if update failed and upsert is True + settings = {"_id": guild_id, **data.get("$set", {})} + await cls._settings_db.insert_one(settings) + cls._settings_buffer[guild_id] = settings + return True + + return result + + except Exception as e: + raise ConnectionError(f"Failed to update settings: {str(e)}") + + @classmethod + async def get_user( + cls, + user_id: int, + *, + d_type: Optional[str] = None, + need_copy: bool = True, + force_refresh: bool = False + ) -> Dict[str, Any]: + """ + Retrieve user data with caching and type-specific data. + + Args: + user_id: The Discord user ID + d_type: Specific data type to retrieve + need_copy: If True, return a deep copy of the data + force_refresh: If True, bypass cache and fetch fresh data + + Returns: + Dict containing user data + + Raises: + ConnectionError: If database operation fails + ValueError: If invalid d_type is provided + """ + try: + async with cls._lock: + # Check if we need fresh data + if force_refresh or user_id not in cls._users_buffer: + user = await cls._users_db.find_one({"_id": user_id}) + if not user: + user = {"_id": user_id, **copy.deepcopy(cls._user_base)} + try: + await cls._users_db.insert_one(user) + except Exception as e: + raise ConnectionError(f"Failed to create user: {str(e)}") + + cls._users_buffer[user_id] = user + cls._last_access[user_id] = time.time() + + user = cls._users_buffer[user_id] + + if d_type: + if d_type not in cls._user_base: + raise ValueError(f"Invalid data type: {d_type}") + user = user.setdefault(d_type, copy.deepcopy(cls._user_base.get(d_type))) + + return copy.deepcopy(user) if need_copy else user + + except Exception as e: + raise ConnectionError(f"Failed to retrieve user data: {str(e)}") + + @classmethod + async def update_user( + cls, + user_id: int, + data: Dict[UpdateOperationType, Dict[str, Any]], + *, + upsert: bool = False + ) -> bool: + """ + Update user data. + + Args: + user_id: The Discord user ID + data: Update operations to perform + upsert: If True, create user if doesn't exist + + Returns: + bool: True if update was successful + + Raises: + ValueError: If invalid update data is provided + ConnectionError: If database operation fails + """ + try: + user = await cls.get_user(user_id, need_copy=False) + result = await cls._update_db( + cls._users_db, + user, + {"_id": user_id}, + data + ) + + if not result and upsert: + # Try to insert if update failed and upsert is True + user_data = {"_id": user_id, **data.get("$set", {})} + await cls._users_db.insert_one(user_data) + cls._users_buffer[user_id] = user_data + return True + + return result + + except Exception as e: + raise ConnectionError(f"Failed to update user: {str(e)}") + + @classmethod + async def delete_user(cls, user_id: int) -> bool: + """ + Delete a user's data completely. + + Args: + user_id: The Discord user ID + + Returns: + bool: True if deletion was successful + + Raises: + ConnectionError: If database operation fails + """ + try: + async with cls._lock: + result = await cls._users_db.delete_one({"_id": user_id}) + if result.deleted_count > 0: + cls._users_buffer.pop(user_id, None) + cls._last_access.pop(user_id, None) + return True + return False + + except Exception as e: + raise ConnectionError(f"Failed to delete user: {str(e)}") + + @classmethod + async def get_users_by_criteria( + cls, + criteria: Dict[str, Any], + *, + limit: Optional[int] = None, + skip: int = 0 + ) -> List[Dict[str, Any]]: + """ + Retrieve multiple users matching specific criteria. + + Args: + criteria: MongoDB query criteria + limit: Maximum number of users to return + skip: Number of matching users to skip + + Returns: + List of matching user data + + Raises: + ConnectionError: If database operation fails + """ + try: + cursor = cls._users_db.find(criteria).skip(skip) + if limit: + cursor = cursor.limit(limit) + + users = await cursor.to_list(length=None) + + # Update cache with fetched users + async with cls._lock: + current_time = time.time() + for user in users: + user_id = user["_id"] + cls._users_buffer[user_id] = user + cls._last_access[user_id] = current_time + + return users + + except Exception as e: + raise ConnectionError(f"Failed to retrieve users: {str(e)}") diff --git a/voicelink/objects.py b/voicelink/objects.py index 2852fd6..44d7f16 100644 --- a/voicelink/objects.py +++ b/voicelink/objects.py @@ -22,18 +22,15 @@ SOFTWARE. """ import re -from typing import Optional -from discord import Member +from typing import Optional from tldextract import extract +from discord import Member from .enums import SearchType -from function import ( - get_source, - time as ctime -) - -from .transformer import encode +from .config import Config +from .utils import format_ms +from .transformer import encode, decode YOUTUBE_REGEX = re.compile(r'(https?://)?(www\.)?youtube\.(com|nl)/watch\?v=([-\w]+)') @@ -67,8 +64,11 @@ class Track: track_id: str = None, info: dict, requester: Member, - search_type: SearchType = SearchType.YOUTUBE, + search_type: SearchType = None, ): + if not search_type: + search_type = Config().search_platform + self._track_id: Optional[str] = track_id self.info: dict = info @@ -83,7 +83,7 @@ class Track: if not self.thumbnail and YOUTUBE_REGEX.match(self.uri): self.thumbnail = f"https://img.youtube.com/vi/{self.identifier}/maxresdefault.jpg" - self.emoji: str = get_source(self.source, "emoji") + self.emoji: str = Config().get_source_config(self.source, "emoji") self.length: float = info.get("length") self.requester: Member = requester @@ -114,7 +114,7 @@ class Track: @property def formatted_length(self) -> str: - return ctime(self.length) + return format_ms(self.length) @property def data(self) -> dict: @@ -123,6 +123,14 @@ class Track: "requester_id": self.requester.id } + @classmethod + def decode(cls, track_id: str) -> dict: + return decode(track_id) + + @classmethod + def encode(cls, track_info: dict) -> 'Track': + return encode(track_info) + class Playlist: """The base playlist object. Returns critical playlist information needed for parsing by Lavalink. @@ -162,4 +170,4 @@ class Playlist: @property def track_count(self) -> int: - return len(self.tracks) + return len(self.tracks) \ No newline at end of file diff --git a/voicelink/placeholders.py b/voicelink/placeholders.py index 407225c..5f5ef5d 100644 --- a/voicelink/placeholders.py +++ b/voicelink/placeholders.py @@ -24,27 +24,30 @@ SOFTWARE. from __future__ import annotations import re -import function as func +import discord -from discord import Embed, Client +from discord.ext import commands +from typing import TYPE_CHECKING, List, Callable -from typing import TYPE_CHECKING +from .config import Config +from .utils import format_ms if TYPE_CHECKING: from .player import Player from .objects import Track - -def ensure_track(func) -> callable: - def wrapper(self: Placeholders, *args, **kwargs): + from .pool import NodePool + +def ensure_track(func) -> Callable: + def wrapper(self: PlayerPlaceholder, *args, **kwargs): current = self.get_current() if not current: return "None" return func(self, current, *args, **kwargs) return wrapper -class Placeholders: - def __init__(self, bot: Client, player: Player = None) -> None: - self.bot: Client = bot +class PlayerPlaceholder: + def __init__(self, bot: commands.Bot, player: Player = None) -> None: + self.bot: commands.Bot = bot self.player: Player = player self.variables = { @@ -67,7 +70,7 @@ class Placeholders: "loop_mode": self.loop_mode, "default_embed_color": self.default_embed_color, "bot_icon": self.bot_icon, - "server_invite_link": func.settings.invite_link, + "server_invite_link": Config().invite_link, "invite_link": f"https://discord.com/oauth2/authorize?client_id={self.bot.user.id}&permissions=2184260928&scope=bot%20applications.commands" } @@ -97,7 +100,7 @@ class Placeholders: @ensure_track def track_duration(self, track: Track) -> str: - return self.player.get_msg("live") if track.is_stream else func.time(track.length) + return self.player.get_msg("common.status.live") if track.is_stream else format_ms(track.length) @ensure_track def track_requester_id(self, track: Track) -> str: @@ -117,7 +120,7 @@ class Placeholders: @ensure_track def track_color(self, track: Track) -> int: - return int(func.get_source(track.source, "color"), 16) + return int(Config().get_source_config(track.source, "color"), 16) @ensure_track def track_source_name(self, track: Track) -> str: @@ -152,7 +155,7 @@ class Placeholders: return self.player.queue.repeat if self.player else "Off" def default_embed_color(self) -> int: - return func.settings.embed_color + return Config().embed_color def bot_icon(self) -> str: return self.bot.user.display_avatar.url if self.player else "https://i.imgur.com/dIFBwU7.png" @@ -197,41 +200,78 @@ class Placeholders: text = re.sub(r'@@(.*?)@@', lambda x: str(variables.get(x.group(1), '')), text) return text -def build_embed(embed_form: dict[str, dict], placeholder: Placeholders) -> Embed: - embed = Embed() - try: - rv = {key: func() if callable(func) else func for key, func in placeholder.variables.items()} - if author := embed_form.get("author"): - embed.set_author( - name = placeholder.replace(author.get("name"), rv), - url = placeholder.replace(author.get("url"), rv), - icon_url = placeholder.replace(author.get("icon_url"), rv) - ) - - if title := embed_form.get("title"): - embed.title = placeholder.replace(title.get("name"), rv) - embed.url = placeholder.replace(title.get("url"), rv) + @classmethod + def build_embed(cls, embed_form: dict[str, dict], placeholder: PlayerPlaceholder) -> discord.Embed: + embed = discord.Embed() + try: + rv = {key: func() if callable(func) else func for key, func in placeholder.variables.items()} + if author := embed_form.get("author"): + embed.set_author( + name = placeholder.replace(author.get("name"), rv), + url = placeholder.replace(author.get("url"), rv), + icon_url = placeholder.replace(author.get("icon_url"), rv) + ) + + if title := embed_form.get("title"): + embed.title = placeholder.replace(title.get("name"), rv) + embed.url = placeholder.replace(title.get("url"), rv) - if fields := embed_form.get("fields", []): - for f in fields: - embed.add_field(name=placeholder.replace(f.get("name"), rv), value=placeholder.replace(f.get("value", ""), rv), inline=f.get("inline", False)) + if fields := embed_form.get("fields", []): + for f in fields: + embed.add_field(name=placeholder.replace(f.get("name"), rv), value=placeholder.replace(f.get("value", ""), rv), inline=f.get("inline", False)) - if footer := embed_form.get("footer"): - embed.set_footer( - text = placeholder.replace(footer.get("text"), rv), - icon_url = placeholder.replace(footer.get("icon_url"), rv) - ) + if footer := embed_form.get("footer"): + embed.set_footer( + text = placeholder.replace(footer.get("text"), rv), + icon_url = placeholder.replace(footer.get("icon_url"), rv) + ) - if thumbnail := embed_form.get("thumbnail"): - embed.set_thumbnail(url = placeholder.replace(thumbnail, rv)) - - if image := embed_form.get("image"): - embed.set_image(url = placeholder.replace(image, rv)) + if thumbnail := embed_form.get("thumbnail"): + embed.set_thumbnail(url = placeholder.replace(thumbnail, rv)) + + if image := embed_form.get("image"): + embed.set_image(url = placeholder.replace(image, rv)) - embed.description = placeholder.replace(embed_form.get("description"), rv) - embed.color = int(placeholder.replace(embed_form.get("color"), rv)) + embed.description = placeholder.replace(embed_form.get("description"), rv) + embed.color = int(placeholder.replace(embed_form.get("color"), rv)) - except: - pass + except: + pass - return embed \ No newline at end of file + return embed + +class BotPlaceholder: + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + self.variables = { + "guilds": self.guilds_count, + "users": self.users_count, + "players": self.players_count, + "nodes": self.nodes_count + } + + def guilds_count(self) -> int: + return len(self.bot.guilds) + + def users_count(self) -> int: + return len(self.bot.users) + + def players_count(self) -> int: + count = 0 + for node in NodePool._nodes.values(): + count += len(node._players) + + return count + + def nodes_count(self): + return len(NodePool._nodes) + + def replace(self, msg: str) -> str: + keys: List[str] = re.findall(r'@@(.*?)@@', msg) + + for key in keys: + value = self.variables.get(key.lower(), None) + if value: + msg = msg.replace(f"@@{key}@@", str(value())) + + return msg \ No newline at end of file diff --git a/voicelink/player.py b/voicelink/player.py index 5a07c80..b2c85ab 100644 --- a/voicelink/player.py +++ b/voicelink/player.py @@ -21,13 +21,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +from __future__ import annotations + import time, logging -import function as func from math import ceil from asyncio import sleep -from views import InteractiveController -from typing import Any, Dict, List, Optional, Union, Tuple +from random import shuffle, choice +from typing import Any, Dict, List, Optional, Union, Tuple, TYPE_CHECKING from discord import ( Client, @@ -45,18 +46,25 @@ from discord import ( from discord.ext import commands from . import events +from .config import Config +from .pool import Node, NodePool +from .objects import Track, Playlist +from .filters import Filter, Filters from .enums import SearchType, LoopType, RequestMethod from .events import VoicelinkEvent, TrackEndEvent, TrackStartEvent, TrackExceptionEvent from .exceptions import VoicelinkException, FilterInvalidArgument, TrackInvalidPosition, FilterTagAlreadyInUse, DuplicateTrack -from .filters import Filter, Filters -from .objects import Track, Playlist -from .pool import Node, NodePool -from .placeholders import Placeholders, build_embed +from .placeholders import PlayerPlaceholder from .queue import Queue, QUEUE_TYPES -from random import shuffle, choice +from .mongodb import MongoDBHandler +from .language import LangHandler +from .views import InteractiveController +from .utils import format_ms, dispatch_message + +if TYPE_CHECKING: + from .ipc import IPCClient async def connect_channel(ctx: Union[commands.Context, Interaction], channel: VoiceChannel = None): - texts = await func.get_lang(ctx.guild.id, "noChannel", "noPermission") + texts = await LangHandler.get_lang(ctx.guild.id, "voice.connection.noChannel", "voice.connection.noPermission") try: channel = channel or ctx.author.voice.channel if isinstance(ctx, commands.Context) else ctx.user.voice.channel except: @@ -66,7 +74,7 @@ async def connect_channel(ctx: Union[commands.Context, Interaction], channel: Vo if check.connect == False or check.speak == False: raise VoicelinkException(texts[1]) - settings = await func.get_settings(channel.guild.id) + settings = await MongoDBHandler.get_settings(channel.guild.id) player: Player = await channel.connect( cls=Player( ctx.bot if isinstance(ctx, commands.Context) else ctx.client, @@ -76,7 +84,7 @@ async def connect_channel(ctx: Union[commands.Context, Interaction], channel: Vo if player.volume != 100: await player.set_volume(player.volume) - if ctx.bot.ipc.is_connected: + if player.is_ipc_connected: await player.send_ws({"op": "createPlayer", "memberIds": [str(member.id) for member in channel.members]}) return player @@ -104,8 +112,8 @@ class Player(VoiceProtocol): ): self.client: Client = client self._bot: Client = client - self._ipc = self._bot.ipc - self._ipc_connection = False + self._ipc_client: IPCClient = self._bot.ipc_client + self._ipc_connection: bool = False self.context = ctx self.dj: Member = ctx.user if isinstance(ctx, Interaction) else ctx.author @@ -116,7 +124,7 @@ class Player(VoiceProtocol): self.joinTime: float = round(time.time()) self._volume: int = self.settings.get('volume', 100) self.queue: Queue = QUEUE_TYPES.get(self.settings.get("queue_type", "queue").lower())( - self.settings.get("max_queue", func.settings.max_queue), + self.settings.get("max_queue", Config().max_queue), self.settings.get("duplicate_track", True), self.get_msg ) @@ -145,7 +153,7 @@ class Player(VoiceProtocol): self.shuffle_votes = set() self.stop_votes = set() - self._ph = Placeholders(client, self) + self._ph = PlayerPlaceholder(client, self) self._logger: Optional[logging.Logger] = self._node._logger def __repr__(self): @@ -252,15 +260,15 @@ class Player(VoiceProtocol): @property def is_ipc_connected(self) -> bool: """Indicates whether the Inter-Process Communication (IPC) connection is active.""" - return self._ipc._is_connected and self._ipc_connection + return self._ipc_client._is_connected and self._ipc_connection def get_msg(self, *keys) -> Union[list[str], str]: """Retrieves a localized message or list of messages based on the given keys for the guild associated with this player. """ - return func._get_lang(self.settings.get("lang"), *keys) + return LangHandler._get_lang(self.settings.get("lang"), *keys) - def required(self, leave=False): + def required(self, leave: bool = False): """ Calculates the number of votes required for a specific action in the voice channel. @@ -271,8 +279,7 @@ class Player(VoiceProtocol): required = ceil((len(self.channel.members) - 1) / 2.5) if leave: - if len(self.channel.members) == 3: - required = 2 + required += 1 return required @@ -291,12 +298,12 @@ class Player(VoiceProtocol): has 'Manage Server' permission, or meets the DJ role criteria in the settings. Raises an exception if `check_user_join` is True and the user is not in the channel. """ - if user.id in func.settings.bot_access_user: + if user.id in Config().bot_access_user: return True manage_perm = user.guild_permissions.manage_guild if check_user_join and not self.is_user_join(user): - raise VoicelinkException(self.get_msg('notInChannel').format(user.mention, self.channel.mention)) + raise VoicelinkException(self.get_msg('voice.connection.notInChannel').format(user.mention, self.channel.mention)) if 'dj' in self.settings and self.settings['dj']: return manage_perm or (self.settings['dj'] in [role.id for role in user.roles]) @@ -304,10 +311,10 @@ class Player(VoiceProtocol): def build_embed(self, current_track: Track = None): """Builds an embed based on the current track state.""" - controller = self.settings.get("default_controller", func.settings.controller).get("embeds", {}) + controller = self.settings.get("default_controller", Config().controller).get("embeds", {}) embed_form = controller.get("active" if current_track else "inactive", {}) - return build_embed(embed_form, self._ph) + return PlayerPlaceholder.build_embed(embed_form, self._ph) async def send(self, method: RequestMethod, query: str = None, data: Union[Dict, str] = {}) -> Dict: """Sends an HTTP request to the node with the given method, query, and data.""" @@ -424,7 +431,7 @@ class Player(VoiceProtocol): return await self.do_next() if not track.requester.bot: - self._bot.loop.create_task(func.update_user(track.requester.id, { + self._bot.loop.create_task(MongoDBHandler.update_user(track.requester.id, { "$push": {"history": {"$each": [track.track_id], "$slice": -25}} })) @@ -463,11 +470,11 @@ class Player(VoiceProtocol): # Send a new controller message if none exists if not self.controller: - self.controller = await func.send(self.context, content=embed, view=view, requires_fetch=True) + self.controller = await dispatch_message(self.context, content=embed, view=view, requires_fetch=True) elif not await self.is_position_fresh(): await self.controller.delete() - self.controller = await func.send(self.context, content=embed, view=view, requires_fetch=True) + self.controller = await dispatch_message(self.context, content=embed, view=view, requires_fetch=True) else: await self.controller.edit(embed=embed, view=view) @@ -495,7 +502,7 @@ class Player(VoiceProtocol): async def teardown(self): """Cleans up the player and associated resources.""" try: - await func.update_settings(self.guild.id, {"$set": { + await MongoDBHandler.update_settings(self.guild.id, {"$set": { "last_active": (timeNow := round(time.time())), "played_time": round(self.settings.get("played_time", 0) + ((timeNow - self.joinTime) / 60), 2) }}) @@ -524,13 +531,16 @@ class Player(VoiceProtocol): query: str, *, requester: Member, - search_type: SearchType = SearchType.YOUTUBE + search_type: SearchType = None ) -> Union[List[Track], Playlist]: """Fetches tracks from the node's REST api to parse into Lavalink. You can also pass in a discord.py Context object to get a Context object on any track you search. """ + if not search_type: + search_type = Config().search_platform + return await self._node.get_tracks(query, requester=requester, search_type=search_type) async def connect(self, *, timeout: float, reconnect: bool, self_deaf: bool = True, self_mute: bool = False): @@ -607,13 +617,13 @@ class Player(VoiceProtocol): end_time = track.length if start_time >= end_time: - raise VoicelinkException(self.get_msg("invalidStartTime")) + raise VoicelinkException(self.get_msg("time.invalidStartTime")) track_length = track.length if not 0 <= start_time <= track_length: - raise VoicelinkException(self.get_msg("invalidStartTime", func.time(track_length))) + raise VoicelinkException(self.get_msg("time.invalidStartTime", format_ms(track_length))) if not 0 <= end_time <= track_length: - raise VoicelinkException(self.get_msg("invalidEndTime", func.time(track_length))) + raise VoicelinkException(self.get_msg("time.invalidEndTime", format_ms(track_length))) track.position = start_time track.end_time = end_time @@ -636,7 +646,7 @@ class Player(VoiceProtocol): _duplicate_tracks.append(track.uri) else: if raw_tracks.uri in _duplicate_tracks: - raise DuplicateTrack(self.get_msg("voicelinkDuplicateTrack")) + raise DuplicateTrack(self.get_msg("queue.errors.duplicateTrack")) self._validate_time(raw_tracks, start_time, end_time) position = self.queue.put_at_front(raw_tracks) if at_front else self.queue.put(raw_tracks) @@ -705,7 +715,7 @@ class Player(VoiceProtocol): """Shuffles the tracks in the specified queue or history.""" replacement = self.queue.tracks() if queue_type == "queue" else self.queue.history() if len(replacement) < 3: - raise VoicelinkException(self.get_msg('shuffleError')) + raise VoicelinkException(self.get_msg('player.controls.shuffle.error')) shuffle(replacement) self.queue.replace(queue_type, replacement) @@ -760,7 +770,7 @@ class Player(VoiceProtocol): try: self._filters.add_filter(filter=filter) except FilterTagAlreadyInUse: - raise FilterTagAlreadyInUse(self.get_msg("filterTagAlreadyInUse")) + raise FilterTagAlreadyInUse(self.get_msg("effects.tagInUse")) payload = self._filters.get_all_payloads() await self.send(method=RequestMethod.PATCH, data={"filters": payload}) @@ -864,7 +874,7 @@ class Player(VoiceProtocol): async def update_voice_status(self, remove_status: bool = False) -> None: """Updates the voice status of the channel based on the specified template.""" - template = self.settings.get("stage_announce_template", func.settings.voice_status_template) + template = self.settings.get("stage_announce_template", Config().voice_status_template) if not template or not self.channel: return @@ -888,4 +898,4 @@ class Player(VoiceProtocol): payload['guildId'] = str(self.guild.id) if requester: payload['requesterId'] = str(requester.id) - await self.bot.ipc.send(payload) \ No newline at end of file + await self._ipc_client.send(payload) \ No newline at end of file diff --git a/voicelink/pool.py b/voicelink/pool.py index ec25f31..2e87dcd 100644 --- a/voicelink/pool.py +++ b/voicelink/pool.py @@ -51,6 +51,7 @@ from .objects import Playlist, Track from .utils import ExponentialBackoff, NodeStats, NodeInfo, Ping from .enums import RequestMethod from .ratelimit import YTRatelimit, YTToken, STRATEGY +from .config import Config if TYPE_CHECKING: from .player import Player @@ -353,7 +354,7 @@ class Node: query: str, *, requester: Member, - search_type: SearchType = SearchType.YOUTUBE + search_type: SearchType = None ) -> Union[List[Track], Playlist]: """ Fetches tracks from the node's REST api to parse into Lavalink. @@ -361,7 +362,9 @@ class Node: You can also pass in a discord.py Context object to get a Context object on any track you search. """ - + if not search_type: + search_type = Config().search_platform + if not URL_REGEX.match(query) and ':' not in query: query = f"{search_type}:{query}" @@ -509,7 +512,7 @@ class NodePool: raise NodeCreationError(f"A node with identifier '{identifier}' already exists.") if not logger: - logger = logging.getLogger("voicelink") + logger = logging.getLogger("vocard") node = Node( pool=cls, bot=bot, host=host, port=port, password=password, diff --git a/voicelink/queue.py b/voicelink/queue.py index 58092e2..dc05ea0 100644 --- a/voicelink/queue.py +++ b/voicelink/queue.py @@ -21,14 +21,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +import discord + +from itertools import cycle +from typing import Optional, Tuple, Callable, Dict, List + from .exceptions import QueueFull, OutofList from .objects import Track from .enums import LoopType -from typing import Optional, Tuple, Callable, Dict, List -from itertools import cycle -from discord import Member - class LoopTypeCycle: def __init__(self) -> None: self._cycle = cycle(LoopType) @@ -87,33 +88,33 @@ class Queue: def put(self, item: Track) -> int: if self.count >= self._size: - raise QueueFull(self.get_msg("voicelinkQueueFull").format(self._size)) + raise QueueFull(self.get_msg("queue.errors.queueFull").format(self._size)) self._queue.append(item) return self.count def put_at_front(self, item: Track) -> int: if self.count >= self._size: - raise QueueFull(self.get_msg("voicelinkQueueFull").format(self._size)) + raise QueueFull(self.get_msg("queue.errors.queueFull").format(self._size)) self._queue.insert(self._position, item) return 1 def put_at_index(self, index: int, item: Track) -> None: if self.count >= self._size: - raise QueueFull(self.get_msg("voicelinkQueueFull").format(self._size)) + raise QueueFull(self.get_msg("queue.errors.queueFull").format(self._size)) return self._queue.insert(self._position - 1 + index, item) def skipto(self, index: int) -> None: if not 0 < index <= self.count: - raise OutofList(self.get_msg("voicelinkOutofList")) + raise OutofList(self.get_msg("queue.errors.outOfList")) else: self._position += index - 1 def backto(self, index: int) -> None: if not self._position - index >= 0: - raise OutofList(self.get_msg("voicelinkOutofList")) + raise OutofList(self.get_msg("queue.errors.outOfList")) else: self._position -= index @@ -137,11 +138,11 @@ class Queue: self._queue[adjusted_position + track_index1], self._queue[adjusted_position + track_index2] = self._queue[adjusted_position + track_index2], self._queue[adjusted_position + track_index1] return self._queue[adjusted_position + track_index1], self._queue[adjusted_position + track_index2] except IndexError: - raise OutofList(self.get_msg("voicelinkOutofList")) + raise OutofList(self.get_msg("queue.errors.outOfList")) def move(self, target: int, to: int) -> Optional[Track]: if not 0 < target <= self.count or not 0 < to: - raise OutofList(self.get_msg("voicelinkOutofList")) + raise OutofList(self.get_msg("queue.errors.outOfList")) try: item = self._queue[self._position + target - 1] @@ -149,9 +150,9 @@ class Queue: self.put_at_index(to, item) return item except: - raise OutofList(self.get_msg("voicelinkOutofList")) + raise OutofList(self.get_msg("queue.errors.outOfList")) - def remove(self, index: int, index2: int = None, member: Member = None) -> Dict[int, Track]: + def remove(self, index: int, index2: int = None, member: discord.Member = None) -> Dict[int, Track]: pos = self._position - 1 if index2 is None: @@ -171,7 +172,7 @@ class Queue: return removed_tracks except: - raise OutofList(self.get_msg("voicelinkOutofList")) + raise OutofList(self.get_msg("queue.errors.outOfList")) def history(self, incTrack: bool = False) -> List[Track]: if incTrack: @@ -206,7 +207,7 @@ class FairQueue(Queue): def put(self, item: Track) -> int: if len(self._queue) >= self._size: - raise QueueFull(self.get_msg("voicelinkQueueFull").format(self._size)) + raise QueueFull(self.get_msg("queue.errors.queueFull").format(self._size)) tracks = self.tracks(incTrack=True) lastIndex = len(tracks) diff --git a/voicelink/ratelimit.py b/voicelink/ratelimit.py index 171a512..c168048 100644 --- a/voicelink/ratelimit.py +++ b/voicelink/ratelimit.py @@ -22,8 +22,9 @@ SOFTWARE. """ import time + from abc import ABC, abstractmethod -from typing import List, Optional, Dict, TYPE_CHECKING, Any +from typing import List, Optional, Dict, Any, TYPE_CHECKING if TYPE_CHECKING: from .pool import Node diff --git a/voicelink/utils.py b/voicelink/utils.py index fbb2689..eecd454 100644 --- a/voicelink/utils.py +++ b/voicelink/utils.py @@ -24,19 +24,25 @@ SOFTWARE. import random import time import socket -from timeit import default_timer as timer +import discord + from itertools import zip_longest - from typing import Dict, Optional +from timeit import default_timer as timer +from discord.ext import commands +from typing import Union -__all__ = [ - "ExponentialBackoff", - "NodeStats", - "NodeInfoVersion", - "NodeInfo", - "Plugin", - "Ping" -] +from .mongodb import MongoDBHandler +from .language import LangHandler + +# __all__ = [ +# "ExponentialBackoff", +# "NodeStats", +# "NodeInfoVersion", +# "NodeInfo", +# "Plugin", +# "Ping" +# ] class ExponentialBackoff: """ @@ -203,4 +209,200 @@ class Ping: ((self._host, self._port), None)) s_runtime = 1000 * (cost_time) - return s_runtime \ No newline at end of file + return s_runtime + +class TempCtx(): + def __init__(self, author: discord.Member, channel: discord.VoiceChannel) -> None: + self.author: discord.Member = author + self.channel: discord.VoiceChannel = channel + self.guild: discord.Guild = channel.guild + +def format_to_ms(time_str: str) -> int: + """ + Converts a time string in one of the formats ('HH:MM:SS', 'MM:SS', 'SS') to milliseconds. + + Args: + time_str (str): The input time string. + + Returns: + int: Time in milliseconds, or 0 if parsing fails. + """ + formats = ['%H:%M:%S', '%M:%S', '%S'] + + for fmt in formats: + try: + parsed = time.strptime(time_str, fmt) + total_seconds = parsed.tm_hour * 3600 + parsed.tm_min * 60 + parsed.tm_sec + return total_seconds * 1000 + except ValueError: + continue + + return 0 + +def format_ms(milliseconds: Union[float, int]) -> str: + """ + Converts milliseconds to a formatted time string. + + Args: + milliseconds (int): The time in milliseconds. + + Returns: + str: The formatted time string. + + Examples: + 65000 -> "01:05" + 3723000 -> "1:02:03" + 90061000 -> "1 days, 01:01:01" + """ + if isinstance(milliseconds, float): + milliseconds = int(milliseconds) + + seconds = (milliseconds // 1000) % 60 + minutes = (milliseconds // 60_000) % 60 + hours = (milliseconds // 3_600_000) % 24 + days = milliseconds // 86_400_000 + + if days: + return f"{days} days, {hours:02}:{minutes:02}:{seconds:02}" + if hours: + return f"{hours}:{minutes:02}:{seconds:02}" + return f"{minutes:02}:{seconds:02}" + +def format_bytes(bytes: int, unit: bool = False): + """ + Converts bytes to a human-readable string in MB or GB. + Args: + bytes (int): The number of bytes. + unit (bool): If True, appends the unit (MB or GB) to the result. + + Returns: + str: The formatted string. + """ + if bytes <= 1_000_000_000: + return f"{bytes / (1024 ** 2):.1f}" + ("MB" if unit else "") + + else: + return f"{bytes / (1024 ** 3):.1f}" + ("GB" if unit else "") + +def truncate_string(text: str, length: int = 40) -> str: + """ + Truncates a string to a specified maximum length, appending '...' if the string exceeds that length. + + Args: + text (str): The input string to truncate. + length (int, optional): The maximum allowed length of the output string, including the ellipsis. Defaults to 40. + + Returns: + str: The truncated string with '...' appended if it was longer than the specified length. + """ + return text[:length - 3] + "..." if len(text) > length else text + +async def dispatch_message( + ctx: Union[commands.Context, discord.Interaction, TempCtx], + content: Union[str, discord.Embed] = None, + *params, + view: discord.ui.View = None, + file: discord.File = None, + delete_after: float = None, + ephemeral: bool = False, + requires_fetch: bool = False +) -> Optional[discord.Message]: + """ + Dispatches a message or embed to the appropriate Discord context. + + Args: + ctx: The command or interaction context. + content: The message content or embed to send. + *params: Parameters to format the content string. + view: Optional UI view. + file: Optional file to attach. + delete_after: Optional auto-delete duration. + ephemeral: Whether the message should be ephemeral. + requires_fetch: Whether to fetch the message after sending. + + Returns: + The sent message object, or None. + """ + if content is None: + content = "No content provided." + + # Determine the text to send + embed = content if isinstance(content, discord.Embed) else None + text = None if embed else content.format(*params) + + # Determine the sending function + send_func = ( + ctx.send if isinstance(ctx, commands.Context) else + ctx.channel.send if isinstance(ctx, TempCtx) else + ctx.followup.send if ctx.response.is_done() else + ctx.response.send_message + ) + + # Check settings for delete_after duration + settings = await MongoDBHandler.get_settings(ctx.guild.id) + send_kwargs = { + "content": text, + "embed": embed, + "allowed_mentions": discord.AllowedMentions().none(), + "silent": settings.get("silent_msg", False), + } + + if file: + send_kwargs["file"] = file + + if view: + send_kwargs["view"] = view + + if "delete_after" in send_func.__code__.co_varnames: + if settings and ctx.channel.id == settings.get("music_request_channel", {}).get("text_channel_id"): + delete_after = 10 + send_kwargs["delete_after"] = delete_after + + if "ephemeral" in send_func.__code__.co_varnames: + send_kwargs["ephemeral"] = ephemeral + + # Send the message or embed + message = await send_func(**send_kwargs) + + if isinstance(message, discord.InteractionCallbackResponse): + message = message.resource + + if requires_fetch and isinstance(message, (discord.WebhookMessage, discord.InteractionMessage)): + message = await message.fetch() + + return message + +async def send_localized_message( + ctx: Union[commands.Context, discord.Interaction, TempCtx], + content_key: str, + *params, + language: str = None, + **kwargs +) -> Optional[discord.Message]: + """ + Sends a localized message using a language key and optional formatting parameters. + + Args: + ctx (Union[commands.Context, discord.Interaction]): The Discord context or interaction. + content_key (str): The key used to retrieve the localized message. + language (str, optional): Language code to override guild default. Must exist in LangHandler.get_all_languages(). + *params: Optional parameters to format the localized message. + **kwargs: Additional keyword arguments passed to dispatch_message (e.g., view, file, delete_after, ephemeral, requires_fetch). + + Returns: + Optional[discord.Message]: The sent message object, or None if sending failed. + """ + if language and language in LangHandler.get_all_languages(): + localized_text = LangHandler._get_lang(language, content_key) + else: + localized_text = await LangHandler.get_lang(ctx.guild.id, content_key) + + if localized_text: + try: + formatted_text = localized_text.format(*params) + except (IndexError, KeyError): + formatted_text = localized_text + else: + formatted_text = "Translation not found." + + return await dispatch_message(ctx, content=formatted_text, **kwargs) \ No newline at end of file diff --git a/views/__init__.py b/voicelink/views/__init__.py similarity index 84% rename from views/__init__.py rename to voicelink/views/__init__.py index 883cfe5..3b124a5 100644 --- a/views/__init__.py +++ b/voicelink/views/__init__.py @@ -21,19 +21,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +from .embed_builder import EmbedBuilderView +from .controller import InteractiveController +from .debug import DebugView +from .link import LinkView +from .inbox import InboxView +from .playlist import PlaylistViewManager +from .lyrics import LyricsView +from .queue import QueueView +from .help import HelpView +from .search import SearchView from discord.ext import commands -class ButtonOnCooldown(commands.CommandError): - def __init__(self, retry_after: float) -> None: - self.retry_after = retry_after - -from .controller import InteractiveController -from .search import SearchView -from .help import HelpView -from .list import ListView -from .lyrics import LyricsView -from .playlist import PlaylistView -from .inbox import InboxView -from .link import LinkView -from .debug import DebugView -from .embedBuilder import EmbedBuilderView diff --git a/views/controller.py b/voicelink/views/controller.py similarity index 75% rename from views/controller.py rename to voicelink/views/controller.py index 9bfc8b8..2d3d903 100644 --- a/views/controller.py +++ b/voicelink/views/controller.py @@ -24,16 +24,23 @@ SOFTWARE. import discord import re import voicelink -import addons -import views -import function as func +import traceback from discord.ext import commands from typing import Optional, Dict, Type, Union, Any +from ..config import Config +from ..utils import format_ms, send_localized_message +from ..language import LangHandler +from ..mongodb import MongoDBHandler + def key(interaction: discord.Interaction): return interaction.user - + +class ButtonOnCooldown(commands.CommandError): + def __init__(self, retry_after: float) -> None: + self.retry_after = retry_after + class ControlButton(discord.ui.Button): def __init__( self, @@ -74,7 +81,7 @@ class ControlButton(discord.ui.Button): async def send(self, interaction: discord.Interaction, key: str, *params, view: discord.ui.View = None, ephemeral: bool = False) -> None: stay = self.player.settings.get("controller_msg", True) - return await func.send( + return await send_localized_message( interaction, key, *params, view=view, delete_after=None if ephemeral or stay else 10, @@ -91,13 +98,13 @@ class Back(ControlButton): async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): if interaction.user in self.player.previous_votes: - return await self.send(interaction, "voted", ephemeral=True) + return await self.send(interaction, "voting.voted", ephemeral=True) else: self.player.previous_votes.add(interaction.user) if len(self.player.previous_votes) >= (required := self.player.required()): pass else: - return await self.send(interaction, "backVote", interaction.user, len(self.player.previous_votes), required) + return await self.send(interaction, "player.controls.back.vote", interaction.user, len(self.player.previous_votes), required) if not self.player.is_playing: self.player.queue.backto(1) @@ -106,7 +113,7 @@ class Back(ControlButton): self.player.queue.backto(2) await self.player.stop() - await self.send(interaction, "backed", interaction.user) + await self.send(interaction, "player.controls.back.success", interaction.user) if self.player.queue._repeat.mode == voicelink.LoopType.TRACK: await self.player.set_repeat(voicelink.LoopType.OFF) @@ -128,7 +135,7 @@ class PlayPause(ControlButton): if not self.player.is_privileged(interaction.user): if interaction.user in votes: - return await self.send(interaction, "voted", ephemeral=True) + return await self.send(interaction, "voting.voted", ephemeral=True) else: votes.add(interaction.user) if len(votes) < (required := self.player.required()): @@ -149,15 +156,15 @@ class Skip(ControlButton): if interaction.user == self.player.current.requester: pass elif interaction.user in self.player.skip_votes: - return await self.send(interaction, "voted", ephemeral=True) + return await self.send(interaction, "voting.voted", ephemeral=True) else: self.player.skip_votes.add(interaction.user) if len(self.player.skip_votes) >= (required := self.player.required()): pass else: - return await self.send(interaction, "skipVote", interaction.user, len(self.player.skip_votes), required) + return await self.send(interaction, "player.controls.skip.vote", interaction.user, len(self.player.skip_votes), required) - await self.send(interaction, "skipped", interaction.user) + await self.send(interaction, "player.controls.skip.success", interaction.user) if self.player.queue._repeat.mode == voicelink.LoopType.TRACK: await self.player.set_repeat(voicelink.LoopType.OFF) @@ -170,15 +177,15 @@ class Stop(ControlButton): async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): if interaction.user in self.player.stop_votes: - return await self.send(interaction, "voted", ephemeral=True) + return await self.send(interaction, "voting.voted", ephemeral=True) else: self.player.stop_votes.add(interaction.user) if len(self.player.stop_votes) >= (required := self.player.required(leave=True)): pass else: - return await self.send(interaction, "leaveVote", interaction.user, len(self.player.stop_votes), required) + return await self.send(interaction, "player.controls.leave.vote", interaction.user, len(self.player.stop_votes), required) - await self.send(interaction, "left", interaction.user) + await self.send(interaction, "player.controls.leave.success", interaction.user) await self.player.teardown() class AddFav(ControlButton): @@ -188,21 +195,21 @@ class AddFav(ControlButton): async def callback(self, interaction: discord.Interaction): track = self.player.current if not track: - return await self.send(interaction, "noTrackPlaying") + return await self.send(interaction, "player.errors.noTrackPlaying") if track.is_stream: - return await self.send(interaction, "playlistAddError") - user = await func.get_user(interaction.user.id, 'playlist') - rank, max_p, max_t = func.check_roles() + return await self.send(interaction, "playlist.errors.streamNotAllowed") + user = await MongoDBHandler.get_user(interaction.user.id, 'playlist') + _, max_t, _ = Config().get_playlist_config() if len(user['200']['tracks']) >= max_t: - return await self.send(interaction, "playlistLimited", max_t, ephemeral=True) + return await self.send(interaction, "playlist.errors.trackLimited", max_t, ephemeral=True) if track.track_id in user['200']['tracks']: - return await self.send(interaction, "playlistRepeated", ephemeral=True) - respond = await func.update_user(interaction.user.id, {"$push": {'playlist.200.tracks': track.track_id}}) + return await self.send(interaction, "playlist.errors.trackRepeated", ephemeral=True) + respond = await MongoDBHandler.update_user(interaction.user.id, {"$push": {'playlist.200.tracks': track.track_id}}) if respond: - await self.send(interaction, "playlistAdded", track.title, interaction.user.mention, user['200']['name'], ephemeral=True) + await self.send(interaction, "playlist.actions.trackAdded", track.title, interaction.user.mention, user['200']['name'], ephemeral=True) else: - await self.send(interaction, "playlistAddError2", ephemeral=True) + await self.send(interaction, "playlist.errors.track", ephemeral=True) class Loop(ControlButton): def __init__(self, **kwargs): @@ -213,7 +220,7 @@ class Loop(ControlButton): async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): - return await self.send(interaction, 'missingModePerm', ephemeral=True) + return await self.send(interaction, 'permissions.missingMode', ephemeral=True) await self.player.set_repeat(requester=interaction.user) self.change_states(self.player.queue._repeat.peek_next().name) @@ -226,12 +233,12 @@ class VolumeUp(ControlButton): async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): - return await self.send(interaction, "missingFunctionPerm") + return await self.send(interaction, "permissions.missingFunction") value = value if (value := self.player.volume + 20) <= 150 else 150 await self.player.set_volume(value, interaction.user) - await self.send(interaction, 'setVolume', value, ephemeral=True) + await self.send(interaction, 'settings.actions.volumeSet', value, ephemeral=True) class VolumeDown(ControlButton): def __init__(self, **kwargs): @@ -239,12 +246,12 @@ class VolumeDown(ControlButton): async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): - return await self.send(interaction, "missingFunctionPerm") + return await self.send(interaction, "permissions.missingFunction") value = value if (value := self.player.volume - 20) >= 0 else 0 await self.player.set_volume(value, interaction.user) - await self.send(interaction, 'setVolume', value, ephemeral=True) + await self.send(interaction, 'settings.actions.volumeSet', value, ephemeral=True) class VolumeMute(ControlButton): def __init__(self, **kwargs): @@ -255,7 +262,7 @@ class VolumeMute(ControlButton): async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): - return await self.send(interaction, "missingFunctionPerm") + return await self.send(interaction, "permissions.missingFunction") is_muted = self.player.volume != 0 value = 0 if is_muted else self.player.settings.get("volume", 100) @@ -269,11 +276,11 @@ class AutoPlay(ControlButton): async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): - return await self.send(interaction, "missingAutoPlayPerm", ephemeral=True) + return await self.send(interaction, "permissions.missingAutoPlay", ephemeral=True) check = not self.player.settings.get("autoplay", False) self.player.settings['autoplay'] = check - await self.send(interaction, 'autoplay', await func.get_lang(interaction.guild_id, 'enabled' if check else "disabled")) + await self.send(interaction, 'autoplay', await LangHandler.get_lang(interaction.guild_id, 'common.status.enabled' if check else "common.status.disabled")) if not self.player.is_playing: await self.player.do_next() @@ -285,16 +292,16 @@ class Shuffle(ControlButton): async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): if interaction.user in self.player.shuffle_votes: - return await self.send(interaction, 'voted', ephemeral=True) + return await self.send(interaction, 'voting.voted', ephemeral=True) else: self.player.shuffle_votes.add(interaction.user) if len(self.player.shuffle_votes) >= (required := self.player.required()): pass else: - return await self.send(interaction, 'shuffleVote', interaction.user, len(self.player.shuffle_votes), required) + return await self.send(interaction, 'player.controls.shuffle.vote', interaction.user, len(self.player.shuffle_votes), required) await self.player.shuffle("queue", interaction.user) - await self.send(interaction, 'shuffled') + await self.send(interaction, 'player.controls.shuffle.success') class Forward(ControlButton): def __init__(self, **kwargs): @@ -305,15 +312,15 @@ class Forward(ControlButton): async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): - return await self.send(interaction, 'missingPosPerm', ephemeral=True) + return await self.send(interaction, 'permissions.missingPosition', ephemeral=True) if not self.player.current: - return await self.send(interaction, 'noTrackPlaying', ephemeral=True) + return await self.send(interaction, 'player.errors.noTrackPlaying', ephemeral=True) position = int(self.player.position + 10000) await self.player.seek(position) - await self.send(interaction, 'forward', func.time(position)) + await self.send(interaction, 'forward', format_ms(position)) class Rewind(ControlButton): def __init__(self, **kwargs): @@ -324,15 +331,15 @@ class Rewind(ControlButton): async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): - return await self.send(interaction, 'missingPosPerm', ephemeral=True) + return await self.send(interaction, 'permissions.missingPosition', ephemeral=True) if not self.player.current: - return await self.send(interaction, 'noTrackPlaying', ephemeral=True) + return await self.send(interaction, 'player.errors.noTrackPlaying', ephemeral=True) position = 0 if (value := int(self.player.position - 30000)) <= 0 else value await self.player.seek(position) - await self.send(interaction, 'rewind', func.time(position)) + await self.send(interaction, 'rewind', format_ms(position)) class Lyrics(ControlButton): def __init__(self, **kwargs): @@ -342,19 +349,20 @@ class Lyrics(ControlButton): ) async def callback(self, interaction: discord.Interaction): + from . import LyricsView if not self.player or not self.player.is_playing: - return await self.send(interaction, "noTrackPlaying", ephemeral=True) + return await self.send(interaction, "player.errors.noTrackPlaying", ephemeral=True) title = self.player.current.title artist = self.player.current.author - lyrics_platform = addons.LYRICS_PLATFORMS.get(func.settings.lyrics_platform) + lyrics_platform = voicelink.LYRICS_PLATFORMS.get(Config().lyrics_platform) if lyrics_platform: lyrics = await lyrics_platform().get_lyrics(title, artist) if not lyrics: - return await self.send(interaction, "lyricsNotFound", ephemeral=True) + return await self.send(interaction, "lyrics.notFound", ephemeral=True) - view = views.LyricsView(name=title, source={_: re.findall(r'.*\n(?:.*\n){,22}', v or "") for _, v in lyrics.items()}, author=interaction.user) + view = LyricsView(name=title, source={_: re.findall(r'.*\n(?:.*\n){,22}', v or "") for _, v in lyrics.items()}, author=interaction.user) view.response = await self.send(interaction, view.build_embed(), view=view, ephemeral=True) class Tracks(discord.ui.Select): @@ -379,13 +387,13 @@ class Tracks(discord.ui.Select): async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): - return await func.send(interaction, "missingFunctionPerm", ephemeral=True) + return await send_localized_message(interaction, "permissions.missingFunction", ephemeral=True) self.player.queue.skipto(int(self.values[0].split(". ")[0])) await self.player.stop() if self.player.settings.get("controller_msg", True): - await func.send(interaction, "skipped", interaction.user) + await send_localized_message(interaction, "player.controls.skip.success", interaction.user) class Effects(discord.ui.Select): def __init__(self, player: "voicelink.Player", btn_data, row): @@ -404,20 +412,20 @@ class Effects(discord.ui.Select): async def callback(self, interaction: discord.Interaction): if not self.player.is_privileged(interaction.user): - return await func.send(interaction, "missingFunctionPerm", ephemeral=True) + return await send_localized_message(interaction, "permissions.missingFunction", ephemeral=True) avalibable_filters = voicelink.Filters.get_available_filters() if self.values[0] == "None": await self.player.reset_filter(requester=interaction.user) - return await func.send(interaction, "clearEffect") + return await send_localized_message(interaction, "effects.cleared") selected_filter = avalibable_filters.get(self.values[0].lower())() if self.player.filters.has_filter(filter_tag=selected_filter.tag): await self.player.remove_filter(filter_tag=selected_filter.tag, requester=interaction.user) - await func.send(interaction, "clearEffect") + await send_localized_message(interaction, "effects.cleared") else: await self.player.add_filter(selected_filter, requester=interaction.user) - await func.send(interaction, "addEffect", selected_filter.tag) + await send_localized_message(interaction, "effects.added", selected_filter.tag) BUTTON_TYPE: Dict[str, Type[Union[ControlButton, discord.ui.Select]]] = { "back": Back, @@ -443,7 +451,7 @@ class InteractiveController(discord.ui.View): super().__init__(timeout=None) self.player: voicelink.Player = player - for row_num, btn_row in enumerate(func.settings.controller.get("buttons")): + for row_num, btn_row in enumerate(Config().controller.get("buttons")): for btn_name, btn_data in btn_row.items(): btn_class = BUTTON_TYPE.get(btn_name.lower()) if not btn_class: @@ -458,27 +466,28 @@ class InteractiveController(discord.ui.View): async def interaction_check(self, interaction: discord.Interaction): if not self.player.node._available: - await func.send(interaction, "nodeReconnect", ephemeral=True) + await send_localized_message(interaction, "player.errors.nodeReconnect", ephemeral=True) return False - if interaction.user.id in func.settings.bot_access_user: + if interaction.user.id in Config().bot_access_user: return True if self.player.channel and self.player.is_user_join(interaction.user): retry_after = self.cooldown.update_rate_limit(interaction) if retry_after: - raise views.ButtonOnCooldown(retry_after) + raise ButtonOnCooldown(retry_after) return True else: - await func.send(interaction, "notInChannel", interaction.user.mention, self.player.channel.mention, ephemeral=True) + await send_localized_message(interaction, "voice.connection.notInChannel", interaction.user.mention, self.player.channel.mention, ephemeral=True) return False async def on_error(self, interaction: discord.Interaction, error: Exception, item: discord.ui.Item) -> None: - if isinstance(error, views.ButtonOnCooldown): + if isinstance(error, ButtonOnCooldown): sec = int(error.retry_after) await interaction.response.send_message(f"You're on cooldown for {sec} second{'' if sec == 1 else 's'}!", ephemeral=True) elif isinstance(error, Exception): + traceback.print_exception(error) await interaction.response.send_message(error) return \ No newline at end of file diff --git a/views/debug.py b/voicelink/views/debug.py similarity index 95% rename from views/debug.py rename to voicelink/views/debug.py index 4f76e10..fac7ebc 100644 --- a/views/debug.py +++ b/voicelink/views/debug.py @@ -24,15 +24,18 @@ SOFTWARE. import discord import io import os +import json import contextlib import textwrap import traceback import voicelink -import function as func from typing import Optional from discord.ext import commands +from ..config import Config +from ..utils import format_ms, format_bytes + class ExecuteModal(discord.ui.Modal): def __init__(self, code: str, *args, **kwargs) -> None: super().__init__(*args, **kwargs) @@ -103,11 +106,7 @@ class AddNodeModal(discord.ui.Modal): await interaction.response.defer() try: - await voicelink.NodePool.create_node( - bot=interaction.client, - logger=func.logger, - **config - ) + await voicelink.NodePool.create_node(bot=interaction.client, **config) await interaction.followup.send(f"Node {self.children[4].value} is connected!", ephemeral=True) await self.view.message.edit(embed=self.view.build_embed(), view=self.view) @@ -278,7 +277,7 @@ class NodesPanel(discord.ui.View): def build_embed(self) -> discord.Embed: self.update_btn_status() - embed = discord.Embed(title="๐Ÿ“ก Nodes Panel", color=func.settings.embed_color) + embed = discord.Embed(title="๐Ÿ“ก Nodes Panel", color=Config().embed_color) if not voicelink.NodePool._nodes: embed.description = "```There are no nodes are connected!```" @@ -295,9 +294,9 @@ class NodesPanel(discord.ui.View): value=f"```โ€ข ADDRESS: {node._host}:{node._port}\n" \ f"โ€ข PLAYERS: {len(node._players)}\n" \ f"โ€ข CPU: {node.stats.cpu_process_load:.1f}%\n" \ - f"โ€ข RAM: {func.format_bytes(node.stats.free)}/{func.format_bytes(total_memory, True)} ({(node.stats.free/total_memory) * 100:.1f}%)\n" + f"โ€ข RAM: {format_bytes(node.stats.free)}/{format_bytes(total_memory, True)} ({(node.stats.free/total_memory) * 100:.1f}%)\n" f"โ€ข LATENCY: {node.latency:.2f}ms\n" \ - f"โ€ข UPTIME: {func.time(node.stats.uptime)}```" + f"โ€ข UPTIME: {format_ms(node.stats.uptime)}```" ) else: embed.add_field( @@ -405,9 +404,9 @@ class DebugView(discord.ui.View): except: pass - session_file_path = os.path.join(func.ROOT_DIR, func.LAST_SESSION_FILE_NAME) - if os.path.exists(session_file_path): - os.remove(session_file_path) + if os.path.exists(Config.LAST_SESSION_FILE_DIR): + os.remove(Config.LAST_SESSION_FILE_DIR) - func.update_json(func.LAST_SESSION_FILE_NAME, player_data) + with open(Config.LAST_SESSION_FILE_DIR, "w", encoding="utf8") as f: + json.dump(player_data, f, ensure_ascii=False, indent=4) await interaction.client.close() \ No newline at end of file diff --git a/views/embedBuilder.py b/voicelink/views/embed_builder.py similarity index 92% rename from views/embedBuilder.py rename to voicelink/views/embed_builder.py index 1e93c91..0a4294d 100644 --- a/views/embedBuilder.py +++ b/voicelink/views/embed_builder.py @@ -21,12 +21,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import discord, copy -import function as func +import discord +import copy from typing import List from discord.ext import commands +from ..mongodb import MongoDBHandler +from ..placeholders import PlayerPlaceholder + class Modal(discord.ui.Modal): def __init__(self, items: List[discord.ui.Item], *args, **kwargs) -> None: super().__init__(*args, **kwargs) @@ -51,28 +54,27 @@ class Dropdown(discord.ui.Select): super().__init__(placeholder='Select a embed to edit...', min_values=1, max_values=1, options=options) async def callback(self, interaction: discord.Interaction): - self.view.embedType = self.values[0].lower() - if self.view.embedType not in self.view.data: - self.view.data[self.view.embedType] = {} + self.view.embed_type = self.values[0].lower() + if self.view.embed_type not in self.view.data: + self.view.data[self.view.embed_type] = {} await interaction.response.edit_message(embed=self.view.build_embed()) class EmbedBuilderView(discord.ui.View): - def __init__(self, context: commands.Context, data: dict) -> None: - from voicelink import Placeholders, build_embed - + def __init__(self, context: commands.Context, placeholder: PlayerPlaceholder, data: dict) -> None: super().__init__(timeout=300) self.add_item(Dropdown()) self.author: discord.Member = context.author + self.ph: PlayerPlaceholder = placeholder self.response: discord.Message = None self.original_data: dict = copy.deepcopy(data) self.data: dict = copy.deepcopy(data) - self.embedType: str = "active" - - self.ph: Placeholders = Placeholders(context.bot) - self.build_embed = lambda: build_embed(self.data.get(self.embedType, {}), self.ph) + self.embed_type: str = "active" + + def build_embed(self) -> discord.Embed: + return PlayerPlaceholder.build_embed(self.data.get(self.embed_type, {}), self.ph) async def on_timeout(self): for child in self.children: @@ -87,7 +89,7 @@ class EmbedBuilderView(discord.ui.View): @discord.ui.button(label="Edit Content", style=discord.ButtonStyle.blurple) async def edit_content(self, interaction: discord.Interaction, button: discord.ui.Button): - data = self.data.get(self.embedType, {}) + data = self.data.get(self.embed_type, {}) items = [ discord.ui.TextInput( label="Title", @@ -144,7 +146,7 @@ class EmbedBuilderView(discord.ui.View): @discord.ui.button(label="Edit Author",) async def edit_author(self, interaction: discord.Interaction, button: discord.ui.Button): - data = self.data.get(self.embedType, {}) + data = self.data.get(self.embed_type, {}) items = [ discord.ui.TextInput( label="Name", @@ -192,7 +194,7 @@ class EmbedBuilderView(discord.ui.View): @discord.ui.button(label="Edit Image") async def edit_image(self, interaction: discord.Interaction, button: discord.ui.Button): - data = self.data.get(self.embedType, {}) + data = self.data.get(self.embed_type, {}) items = [ discord.ui.TextInput( label="Thumbnail", @@ -225,7 +227,7 @@ class EmbedBuilderView(discord.ui.View): @discord.ui.button(label="Edit Footer") async def edit_footer(self, interaction: discord.Interaction, button: discord.ui.Button): - data = self.data.get(self.embedType, {}) + data = self.data.get(self.embed_type, {}) items = [ discord.ui.TextInput( label="Text", @@ -260,7 +262,7 @@ class EmbedBuilderView(discord.ui.View): @discord.ui.button(label="Add Field", style=discord.ButtonStyle.green, row=1) async def add_field(self, interaction: discord.Interaction, button: discord.ui.Button): - data = self.data.get(self.embedType) + data = self.data.get(self.embed_type) items = [ discord.ui.TextInput( label="Name", @@ -310,7 +312,7 @@ class EmbedBuilderView(discord.ui.View): ) ] - data = self.data.get(self.embedType) + data = self.data.get(self.embed_type) if "fields" not in data: data["fields"] = [] @@ -330,7 +332,7 @@ class EmbedBuilderView(discord.ui.View): @discord.ui.button(label="Apply", style=discord.ButtonStyle.green, row=1) async def apply(self, interaction: discord.Interaction, button: discord.ui.Button): - await func.update_settings( + await MongoDBHandler.update_settings( interaction.guild_id, {"$set": {"default_controller.embeds": self.data}}, ) diff --git a/views/help.py b/voicelink/views/help.py similarity index 97% rename from views/help.py rename to voicelink/views/help.py index fcb4005..f24dc3a 100644 --- a/views/help.py +++ b/voicelink/views/help.py @@ -24,10 +24,10 @@ SOFTWARE. import discord from discord.ext import commands -import function as func +from ..config import Config class HelpDropdown(discord.ui.Select): - def __init__(self, categories:list): + def __init__(self, categories: list[str]) -> None: self.view: HelpView super().__init__( @@ -80,7 +80,7 @@ class HelpView(discord.ui.View): def build_embed(self, category: str) -> discord.Embed: category = category.lower() if category == "news": - embed = discord.Embed(title="Vocard Help Menu", url="https://discord.com/channels/811542332678996008/811909963718459392/1069971173116481636", color=func.settings.embed_color) + embed = discord.Embed(title="Vocard Help Menu", url="https://discord.com/channels/811542332678996008/811909963718459392/1069971173116481636", color=Config().embed_color) embed.add_field( name=f"Available Categories: [{2 + len(self.categories)}]", value="```py\n๐Ÿ‘‰ News\n2. Tutorial\n{}```".format("".join(f"{i}. {c}\n" for i, c in enumerate(self.categories, start=3))), @@ -93,7 +93,7 @@ class HelpView(discord.ui.View): return embed - embed = discord.Embed(title=f"Category: {category.capitalize()}", color=func.settings.embed_color) + embed = discord.Embed(title=f"Category: {category.capitalize()}", color=Config().embed_color) embed.add_field(name=f"Categories: [{2 + len(self.categories)}]", value="```py\n" + "\n".join(("๐Ÿ‘‰ " if c == category.capitalize() else f"{i}. ") + c for i, c in enumerate(['News', 'Tutorial'] + self.categories, start=1)) + "```", inline=True) if category == 'tutorial': diff --git a/views/inbox.py b/voicelink/views/inbox.py similarity index 98% rename from views/inbox.py rename to voicelink/views/inbox.py index 7524b7b..db23cc0 100644 --- a/views/inbox.py +++ b/voicelink/views/inbox.py @@ -21,11 +21,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import discord, time -import function as func +import discord +import time from typing import Any +from ..config import Config + class Select_message(discord.ui.Select): def __init__(self, inbox): self.view: InboxView @@ -59,7 +61,7 @@ class InboxView(discord.ui.View): embed=discord.Embed( title=f"๐Ÿ“ญ All {self.author.display_name}'s Inbox", description=f'Max Messages: {len(self.inbox)}/10' + '```%0s %2s %20s\n' % (" ", "ID:", "Title:") + '\n'.join('%0s %2s. %35s'% ('โœ‰๏ธ' if mail['type'] == 'invite' else '๐Ÿ“ข', index, mail['title'][:35] + "...") for index, mail in enumerate(self.inbox, start=1)) + '```', - color=func.settings.embed_color + color=Config().embed_color ) if self.current: diff --git a/views/link.py b/voicelink/views/link.py similarity index 99% rename from views/link.py rename to voicelink/views/link.py index 0ceb3d5..9c1fada 100644 --- a/views/link.py +++ b/voicelink/views/link.py @@ -26,4 +26,4 @@ import discord class LinkView(discord.ui.View): def __init__(self, label=None, emoji=None, url=None): super().__init__(timeout=60) - self.add_item(discord.ui.Button(label=label, emoji=emoji, url=url)) \ No newline at end of file + self.add_item(discord.ui.Button(label=label, emoji=emoji, url=url)) diff --git a/voicelink/views/lyrics.py b/voicelink/views/lyrics.py new file mode 100644 index 0000000..f23f38e --- /dev/null +++ b/voicelink/views/lyrics.py @@ -0,0 +1,81 @@ +"""MIT License + +Copyright (c) 2023 - present Vocard Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import discord + +from .utils import Pagination +from .pagination import PaginationView +from ..config import Config +from ..language import LangHandler + +# class LyricsDropdown(discord.ui.Select): +# def __init__(self, langs: list[str]) -> None: +# self.view: LyricsView + +# super().__init__( +# placeholder="Select A Lyrics Translation", +# min_values=1, max_values=1, +# options=[discord.SelectOption(label=lang) for lang in langs], +# custom_id="selectLyricsLangs" +# ) + +# async def callback(self, interaction: discord.Interaction) -> None: +# self.view.lang = self.values[0] +# self.view.current_page = 1 +# self.view.pages = len(self.view.source.get(self.values[0])) +# await interaction.response.edit_message(embed=self.view.build_embed()) + +class LyricsView(PaginationView): + def __init__(self, name: str, source: dict, author: discord.Member) -> None: + self.name: str = name + self.author: discord.Member = author + self.response: discord.Message = None + + super().__init__( + Pagination[str](source.get("default"), page_size=1), + author + ) + + async def on_timeout(self) -> None: + for child in self.children: + child.disabled = True + try: + await self.response.edit(view=self) + except: + pass + + async def build_embed(self) -> discord.Embed: + page = self.pagination.get_current_page_items() + text = await LangHandler.get_lang(self.author.guild.id, "search.title") + embed=discord.Embed(description=page[0], color=Config().embed_color) + embed.set_author(name=text.format(self.name), icon_url=self.author.display_avatar.url) + return embed + + async def update_message(self, interaction: discord.Interaction) -> None: + """Update the view and edit the message.""" + self.update_view() + + if interaction.response.is_done(): + await interaction.followup.edit_message(self.response.id, embed=await self.build_embed(), view=self) + else: + await interaction.response.edit_message(embed=await self.build_embed(), view=self) \ No newline at end of file diff --git a/voicelink/views/pagination.py b/voicelink/views/pagination.py new file mode 100644 index 0000000..16742b2 --- /dev/null +++ b/voicelink/views/pagination.py @@ -0,0 +1,132 @@ +"""MIT License + +Copyright (c) 2023 - present Vocard Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import discord + +from typing import Dict, Optional + +from .utils import Pagination, BaseModal +from ..language import LangHandler +from ..mongodb import MongoDBHandler + +class PaginationView(discord.ui.View): + def __init__(self, pagination: Pagination, author: discord.Member, timeout: float = 300): + super().__init__(timeout=timeout) + + self.pagination: Pagination = pagination + self.author: discord.Member = author + self.lang: str = MongoDBHandler.get_cached_settings(author.guild.id).get("lang", "EN") + self.update_view() + + def update_view(self, extra_states: Optional[Dict[str, Dict[str, str]]] = None) -> None: + """Update button states and labels based on the current pagination state.""" + texts = LangHandler._get_lang(self.lang, "pagination.prev", "pagination.next") + button_states = { + "fast_back": { + "disabled": self.pagination.current_page <= 1, + "label": "<<", + }, + "back": { + "disabled": not self.pagination.has_previous_page, + "label": texts[0], + }, + "next": { + "disabled": not self.pagination.has_next_page, + "label": texts[1], + }, + "fast_next": { + "disabled": self.pagination.current_page >= self.pagination.total_pages - 1, + "label": ">>", + }, + "page_number": { + "disabled": False, + "label": f"{self.pagination.current_page:02}/{self.pagination.total_pages:02}", + }, + } + + if extra_states: + button_states.update(extra_states) + + # Update button states and labels + for child in self.children: + if (state := button_states.get(child.custom_id)): + child.disabled = state.get("disabled", False) + child.label = state.get("label") + + async def on_error(self, error: Exception, item: discord.ui.Item, interaction: discord.Interaction) -> None: + return + + async def update_message(self, interaction: discord.Interaction) -> None: + """Update the view and edit the message.""" + + @discord.ui.button(label='<<', custom_id="fast_back") + async def fast_back_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: + """Jump to the first page.""" + self.pagination.go_page(0) + await self.update_message(interaction) + + @discord.ui.button(label='Back', custom_id="back", style=discord.ButtonStyle.blurple) + async def back_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: + """Go to the previous page if it exists.""" + self.pagination.go_back() + await self.update_message(interaction) + + @discord.ui.button(label="--/--", custom_id="page_number", style=discord.ButtonStyle.blurple) + async def page_number(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: + """Display current page number.""" + texts = LangHandler._get_lang(self.lang, "pagination.page.title", "pagination.page.field") + modal = BaseModal( + title=texts[0], + custom_id="page_number_modal", + items=[ + discord.ui.TextInput( + label=texts[1], + custom_id="page_number", + placeholder="e.g. 1", + default=str(self.pagination.current_page), + max_length=5, + required=True + ) + ] + ) + await interaction.response.send_modal(modal) + await modal.wait() + + page_number = modal.values.get("page_number") + if not page_number or not page_number.isdigit(): + return + + self.pagination.go_page(int(page_number) - 1) + await self.update_message(interaction) + + @discord.ui.button(label='Next', custom_id="next", style=discord.ButtonStyle.blurple) + async def next_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: + """Go to the next page if it exists.""" + self.pagination.go_next() + await self.update_message(interaction) + + @discord.ui.button(label='>>', custom_id="fast_next") + async def fast_next_button(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: + """Jump to the last page.""" + self.pagination.go_page(self.pagination.total_pages - 1) + await self.update_message(interaction) \ No newline at end of file diff --git a/voicelink/views/playlist.py b/voicelink/views/playlist.py new file mode 100644 index 0000000..a4230e4 --- /dev/null +++ b/voicelink/views/playlist.py @@ -0,0 +1,271 @@ +"""MIT License + +Copyright (c) 2023 - present Vocard Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import discord +import unicodedata + +from tldextract import extract +from discord.ext import commands +from typing import Any + +from .utils import DynamicViewManager, Pagination +from .pagination import PaginationView +from ..config import Config +from ..utils import format_ms, truncate_string +from ..mongodb import MongoDBHandler +from ..language import LangHandler + +class PlaylistDropdown(discord.ui.Select): + def __init__(self, results: list[dict[str, Any]], lang: str) -> None: + self.view: PlaylistViewManager + + super().__init__( + placeholder=LangHandler._get_lang(lang, "playlist.view.playlistSelect"), + custom_id="selector", + options=[ + discord.SelectOption( + emoji=playlist['emoji'], + label=f'{index}. {playlist["name"]}', + value=playlist["id"], + description=f"{playlist['time']} ยท {playlist['type']}" + ) for index, playlist in enumerate(results, start=1) if playlist['type'] != 'error' + ] + ) + + async def callback(self, interaction: discord.Interaction) -> None: + view: PlaylistView = self.view.change_view(self.values[0]) + await interaction.response.edit_message(embed=view.build_embed(), view=view) + + +class PlaylistView(PaginationView): + def __init__( + self, + primary_view: "PlaylistViewManager", + playlist_data: dict[str, Any] + ) -> None: + self.primary_view: PlaylistViewManager = primary_view + self.author: discord.Member = primary_view.ctx.author + + self.playlist_id: str = playlist_data.get("id") + self.emoji: str = playlist_data.get("emoji") + self.name: str = playlist_data.get("name") + self.time: str = playlist_data.get("time") + self.type: str = playlist_data.get("type") + self.owner_id: int = playlist_data.get("owner") + self.perms: dict[str, list[int]] = playlist_data.get("perms") + + super().__init__(Pagination[dict[str, Any]](playlist_data.get("tracks"), page_size=7), primary_view.ctx.author) + + def build_embed(self) -> discord.Embed: + """Build the embed for the current page of tracks.""" + tracks = self.pagination.get_current_page_items() + texts = LangHandler._get_lang( + self.primary_view.lang, + "playlist.view.detailTitle", "playlist.view.detailDesc", "settings.permissions.title", + "playlist.view.permsValue", "playlist.view.permsValue2", + "playlist.view.trackList", "playlist.errors.noTrack", "playlist.view.footer2" + ) + + embed = discord.Embed(title=texts[0], color=Config().embed_color) + embed.description = texts[1].format( + self.name, + self.playlist_id, + self.pagination.total_items, + self.primary_view.ctx.bot.get_user(self.owner_id), + self.type.upper() + ) + "\n" + + embed.description += texts[2] + "\n" + if self.type == 'share': + write_perm = 'โœ“' if 'write' in self.perms and self.author.id in self.perms['write'] else 'โœ˜' + remove_perm = 'โœ“' if 'remove' in self.perms and self.author.id in self.perms['remove'] else 'โœ˜' + embed.description += texts[3].format(write_perm, remove_perm) + else: + readable_users = ', '.join(f'<@{user}>' for user in self.perms['read']) + embed.description += texts[4].format(readable_users) + + # Add track information + embed.description += f"\n\n**{texts[5]}:**\n" + if tracks: + for index, track in enumerate(tracks, start=self.pagination.start_index + 1): + if self.type == "playlist": + source_emoji = Config().get_source_config(track['sourceName'], 'emoji') + track_info = f"{source_emoji} `{index:>2}.` `[{format_ms(track['length'])}]` [{truncate_string(track['title'])}]({track['uri']})" + else: + source_emoji = Config().get_source_config(extract(track.info['uri']).domain, 'emoji') + track_info = f"{source_emoji} `{index:>2}.` `[{format_ms(track.length)}]` [{truncate_string(track.title)}]({track.uri})" + embed.description += track_info + "\n" + else: + embed.description += texts[6].format(self.name) + + # Set the footer + embed.set_footer(text=texts[7].format(self.time)) + return embed + + def update_view(self, extra_states = None): + texts = LangHandler._get_lang(self.lang, "pagination.play", "pagination.share", "pagination.export", "pagination.delete") + extra_states = { + "play": { + "label": texts[0], + }, + "share": { + "label": texts[1], + }, + "export": { + "label": texts[2], + }, + "delete": { + "label": texts[3], + }, + } + return super().update_view(extra_states) + + async def update_message(self, interaction: discord.Interaction) -> None: + """Update the view and edit the message with the new embed.""" + self.update_view() + + if interaction.response.is_done(): + await interaction.followup.edit_message(self.primary_view.response.id, embed=self.build_embed(), view=self) + else: + await interaction.response.edit_message(embed=self.build_embed(), view=self) + + @discord.ui.button(label="<") + async def back_to_home(self, interaction: discord.Interaction, button: discord.ui.Button) -> None: + """Return to the main playlist view.""" + view: PlaylistViewManager = self.primary_view.change_view("home") + await interaction.response.edit_message(embed=view.build_embed(), view=view) + + @discord.ui.button(label="Play", custom_id="play", style=discord.ButtonStyle.green) + async def play_all(self, interaction: discord.Interaction[commands.Bot], button: discord.ui.Button) -> None: + await interaction.response.defer() + cmd = interaction.client.get_command("playlist play") + await cmd(self.primary_view.ctx, self.name) + + @discord.ui.button(label="Share", custom_id="share", style=discord.ButtonStyle.gray) + async def share(self, interaction: discord.Interaction[commands.Bot], button: discord.ui.Button) -> None: + await interaction.response.defer() + + @discord.ui.button(label="Export", custom_id="export", style=discord.ButtonStyle.gray) + async def export(self, interaction: discord.Interaction[commands.Bot], button: discord.ui.Button) -> None: + await interaction.response.defer() + cmd = interaction.client.get_command("playlist export") + await cmd(self.primary_view.ctx, self.name) + + @discord.ui.button(label="Delete", custom_id="delete", style=discord.ButtonStyle.red) + async def delete(self, interaction: discord.Interaction[commands.Bot], button: discord.ui.Button) -> None: + await interaction.response.defer() + cmd = interaction.client.get_command("playlist delete") + await cmd(self.primary_view.ctx, name=self.name) + + if self.playlist_id != "200": + view: PlaylistViewManager = self.primary_view.change_view("home") + view.results = [item for item in view.results if item.get("id") != self.playlist_id] + view.clear_items() + view.add_item(PlaylistDropdown(view.results, view.lang)) + self.primary_view.remove_view(self.playlist_id) + await view.response.edit(embed=view.build_embed(), view=view) + +class PlaylistViewManager(DynamicViewManager): + def __init__(self, ctx: commands.Context, results: list[dict[str, Any]]): + self.ctx: commands.Context[commands.Bot] = ctx + self.results: list[dict[str, Any]] = results + self.lang: str = MongoDBHandler.get_cached_settings(ctx.guild.id).get("lang", "EN") + + views = {"home": self} + views.update({result["id"]: PlaylistView(self, result) for result in results}) + + super().__init__(views=views, timeout=None) + + self.response: discord.Message = None + self.add_item(PlaylistDropdown(results, self.lang)) + + def get_width(self, s): + width = 0 + for char in str(s): + if unicodedata.east_asian_width(char) in ('F', 'W'): + width += 2 + else: + width += 1 + return width + + def pad_string(self, s, width): + s = str(s) + current_width = self.get_width(s) + padding = width - current_width + return s + " " * padding + + async def on_timeout(self) -> None: + for view in self._views.values(): + view.stop() + + for child in self.current_view.children: + child.disabled = True + + try: + await self.response.edit(view=self) + except: + pass + + def build_embed(self) -> discord.Embed: + """ + Build the embed for the playlist overview. + + Returns: + discord.Embed: The constructed embed with playlist details. + """ + max_p, _, _ = Config().get_playlist_config() + text = LangHandler._get_lang(self.lang, "playlist.view.title", "playlist.view.headers", "playlist.view.footer") + + headers = text[1].split(",") + headers.insert(0, "") + content = [headers] + + description = "" + for index in range(max_p): + info = self.results[index] if index < len(self.results) else {} + if info: + content.append([ + info.get('emoji', ' '), + info.get('id', "-" * 3), + f"[{info.get('time', '--:--')}]", + truncate_string(info.get('name', "-" * 6), 12), + len(info.get('tracks', [])) + ]) + + column_widths = [max(self.get_width(str(item)) for item in column) for column in zip(*content)] + + for row in content: + formatted_row = " ".join(self.pad_string(item, width) for item, width in zip(row, column_widths)) + description += formatted_row + "\n" + + embed = discord.Embed( + description=f'```{description}```', + color=Config().embed_color + ) + + embed.set_author( + name=text[0].format(self.ctx.author.display_name), + icon_url=self.ctx.author.display_avatar.url + ) + embed.set_footer(text=text[2]) + return embed \ No newline at end of file diff --git a/voicelink/views/queue.py b/voicelink/views/queue.py new file mode 100644 index 0000000..2f339d2 --- /dev/null +++ b/voicelink/views/queue.py @@ -0,0 +1,132 @@ +"""MIT License + +Copyright (c) 2023 - present Vocard Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from __future__ import annotations + +import discord + +from typing import TYPE_CHECKING + +from .utils import Pagination, BaseModal +from .pagination import PaginationView + +from ..language import LangHandler +from ..config import Config +from ..utils import format_ms, truncate_string + +if TYPE_CHECKING: + from ..player import Player + from ..objects import Track + +class QueueView(PaginationView): + """ + A Discord UI view for displaying and interacting with a paginated list of tracks. + + Attributes: + player (Player): The player containing the track queue or history. + author (discord.Member): The member who initiated the view. + is_queue (bool): Indicates if the list is a queue or history. + pagination (Pagination[Track]): Manages pagination of track lists. + response (discord.Message): The message containing the view. + total_duration (str): The total duration of the tracks. + """ + + def __init__(self, player: "Player", author: discord.Member, is_queue: bool = True) -> None: + + self.player: Player = player + self.is_queue: bool = is_queue + self.total_duration = self.calculate_total_duration() + self.response: discord.Message = None + + super().__init__( + Pagination[Track]( + items=player.queue.tracks() if is_queue else list(reversed(player.queue.history())), + page_size=7, + ), + author + ) + + def calculate_total_duration(self) -> str: + """Calculate the total duration of the tracks.""" + try: + return format_ms(sum(track.length for track in self.pagination._items)) + except Exception: + return "โˆž" + + def format_description(self, tracks: list["Track"], texts: list[str]) -> str: + """Format the description for the embed based on current tracks.""" + now_playing = ( + texts[1].format(self.player.current.uri, + f"```{self.player.current.title}```") + if self.player.current else texts[2].format("None") + ) + + track_list = "\n".join([ + f"{track.emoji} `{i:>2}.` `[{texts[3] if track.is_stream else format_ms(track.length)}]` " + f"[{truncate_string(track.title)}]({track.uri}) {track.requester.mention}" + for i, track in enumerate(tracks, start=self.pagination.start_index + 1) + ]) + + return f"{now_playing}\n**{texts[4] if self.is_queue else texts[5]}**\n{track_list}" + + async def on_timeout(self) -> None: + """Disable all buttons when the view times out.""" + for child in self.children: + child.disabled = True + try: + await self.response.edit(view=self) + except discord.HTTPException: + pass + + async def build_embed(self) -> discord.Embed: + """Build the embed for the current page of tracks.""" + tracks = self.pagination.get_current_page_items() + texts = await LangHandler.get_lang( + self.author.guild.id, + "queue.view.title", + "queue.view.desc", + "player.playback.nowplayingDesc", + "common.status.live", + "queue.management.title", + "queue.view.historyTitle", + "playlist.view.footer2", + ) + + embed = discord.Embed(title=texts[0], color=Config().embed_color) + embed.description = self.format_description(tracks, texts) + + if self.player.current: + embed.set_thumbnail(url=self.player.current.thumbnail) + + embed.set_footer(text=texts[6].format(self.total_duration)) + + return embed + + async def update_message(self, interaction: discord.Interaction) -> None: + """Update the view and edit the message with the new embed.""" + super().update_view() + + if interaction.response.is_done(): + await interaction.followup.edit_message(self.response.id, embed=await self.build_embed(), view=self) + else: + await interaction.response.edit_message(embed=await self.build_embed(), view=self) \ No newline at end of file diff --git a/views/search.py b/voicelink/views/search.py similarity index 95% rename from views/search.py rename to voicelink/views/search.py index 3a77cb8..39e9023 100644 --- a/views/search.py +++ b/voicelink/views/search.py @@ -20,13 +20,10 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from __future__ import annotations import discord -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from voicelink import Track +from ..objects import Track class SearchDropdown(discord.ui.Select): def __init__(self, tracks: list[Track], texts: list[str]) -> None: diff --git a/voicelink/views/utils/__init__.py b/voicelink/views/utils/__init__.py new file mode 100644 index 0000000..a0d3fda --- /dev/null +++ b/voicelink/views/utils/__init__.py @@ -0,0 +1,26 @@ +"""MIT License + +Copyright (c) 2023 - present Vocard Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from .pagination import Pagination +from .modal import BaseModal +from .dynamic_view_manager import DynamicViewManager diff --git a/voicelink/views/utils/dynamic_view_manager.py b/voicelink/views/utils/dynamic_view_manager.py new file mode 100644 index 0000000..d8f1afa --- /dev/null +++ b/voicelink/views/utils/dynamic_view_manager.py @@ -0,0 +1,90 @@ +"""MIT License + +Copyright (c) 2023 - present Vocard Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import discord + + +class DynamicViewManager(discord.ui.View): + def __init__(self, views: dict[str, discord.ui.View], *, timeout: float = 180): + """A manager for multiple dynamic views.""" + super().__init__(timeout=timeout) + + self._views: dict[str, discord.ui.View] = views + self._current_view: discord.ui.View | None = None + + def add_view(self, name: str, view: discord.ui.View) -> None: + """ + Add a view to the manager. + + Args: + name (str): The name of the view. + view (discord.ui.View): The view instance to add. + """ + if name in self._views: + raise ValueError(f"A view with the name '{name}' already exists.") + + self._views[name] = view + + def get_view(self, key: str) -> discord.ui.View | None: + """ + Get a view from the manager. + + Args: + key (str): The name of the view to retrieve. + + Returns: + discord.ui.View | None: The requested view or None if not found. + """ + return self._views.get(key) + + def remove_view(self, key: str) -> None: + """ + Remove a view from the manager. + + Args: + key (str): The name of the view to remove. + """ + if key in self._views: + del self._views[key] + + def change_view(self, name: str) -> discord.ui.View: + """ + Change the current active view. + + Args: + name (str): The name of the view to switch to. + + Returns: + discord.ui.View: The newly activated view. + """ + view = self.get_view(name) + if not view: + raise ValueError(f"No view found with the name '{name}'.") + + self._current_view = view + return view + + @property + def current_view(self) -> discord.ui.View | None: + """Get the current active view.""" + return self._current_view diff --git a/voicelink/views/utils/modal.py b/voicelink/views/utils/modal.py new file mode 100644 index 0000000..ea143be --- /dev/null +++ b/voicelink/views/utils/modal.py @@ -0,0 +1,44 @@ +"""MIT License + +Copyright (c) 2023 - present Vocard Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import discord + + +class BaseModal(discord.ui.Modal): + def __init__(self, title: str, items: list[discord.ui.Item], timeout: float = None, custom_id: str = None) -> None: + super().__init__(title=title, timeout=timeout, custom_id=custom_id) + self._add_items(items) + self.values: dict[str, str] = {} + + def _add_items(self, items: list[discord.ui.Item]) -> None: + """Add items to the modal.""" + for item in items: + self.add_item(item) + + async def on_submit(self, interaction: discord.Interaction) -> None: + """Handle the modal submission.""" + await interaction.response.defer() + + for item in self.walk_children(): + if isinstance(item, discord.ui.TextInput): + self.values[item.custom_id] = item.value diff --git a/voicelink/views/utils/pagination.py b/voicelink/views/utils/pagination.py new file mode 100644 index 0000000..3a48070 --- /dev/null +++ b/voicelink/views/utils/pagination.py @@ -0,0 +1,168 @@ +"""MIT License + +Copyright (c) 2023 - present Vocard Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from typing import List, Generic, TypeVar +from math import ceil + +T = TypeVar("T") + + +class Pagination(Generic[T]): + """ + A class to manage pagination for a list of items. + + Attributes: + items (List[T]): The list of items to paginate. + page_size (int): The number of items per page. + current_page (int): The current page index (0-based). + total_pages (int): The total number of pages. + """ + + def __init__(self, items: List[T], page_size: int): + """ + Initializes the Pagination class. + + Args: + items (List[T]): The list of items to paginate. + page_size (int): The number of items per page. + + Raises: + ValueError: If page_size is less than or equal to 0. + """ + if page_size <= 0: + raise ValueError("page_size must be greater than 0") + + self._items: List[T] = items + self._page_size: int = page_size + self._current_page: int = 0 + self.total_pages: int = ceil(len(items) / page_size) + + def add_item(self, item: T) -> None: + """ + Adds an item to the pagination and updates total pages. + + Args: + item (T): The item to add. + """ + self._items.append(item) + self.total_pages = ceil(len(self._items) / self._page_size) + + def remove_item(self, item: T) -> None: + """ + Removes an item from the pagination and updates total pages. + + Args: + item (T): The item to remove. + """ + self._items.remove(item) + self.total_pages = ceil(len(self._items) / self._page_size) + if self._current_page >= self.total_pages: + self._current_page = max(0, self.total_pages - 1) + + def get_current_page_items(self) -> List[T]: + """ + Retrieves the items for the current page. + + Returns: + List[T]: The items on the current page. + """ + return self._items[self.start_index:self.end_index] + + def go_back(self) -> None: + """Moves to the previous page if available.""" + if self.has_previous_page: + self._current_page -= 1 + + def go_next(self) -> None: + """Moves to the next page if available.""" + if self.has_next_page: + self._current_page += 1 + + def go_page(self, page_number: int) -> None: + """ + Navigate to a specific page, clamped between 0 and total_pages. + + Args: + page_number (int): The page number to navigate to (0-based). + """ + self._current_page = max(0, min(page_number, self.total_pages - 1)) + + @property + def has_next_page(self) -> bool: + """ + Checks if there is a next page. + + Returns: + bool: True if there is a next page, False otherwise. + """ + return self._current_page < self.total_pages - 1 + + @property + def has_previous_page(self) -> bool: + """ + Checks if there is a previous page. + + Returns: + bool: True if there is a previous page, False otherwise. + """ + return self._current_page > 0 + + @property + def start_index(self) -> int: + """ + Gets the start index of the items for the current page. + + Returns: + int: The start index (0-based) of the current page. + """ + return self._current_page * self._page_size + + @property + def end_index(self) -> int: + """ + Gets the end index of the items for the current page. + + Returns: + int: The end index (exclusive) of the current page. + """ + return min(self.start_index + self._page_size, len(self._items)) + + @property + def current_page(self) -> int: + """ + Returns the current page number (1-based). + + Returns: + int: The current page number, starting from 1. + """ + return self._current_page + 1 + + @property + def total_items(self) -> int: + """ + Gets the total number of items in the pagination. + + Returns: + int: The total number of items across all pages. + """ + return len(self._items) \ No newline at end of file