diff --git a/redbot/cogs/alias/alias.py b/redbot/cogs/alias/alias.py index dc40dc308..1c355b82e 100644 --- a/redbot/cogs/alias/alias.py +++ b/redbot/cogs/alias/alias.py @@ -170,7 +170,7 @@ class Alias(commands.Cog): for p in prefixes: if content.startswith(p): return p - raise ValueError(_("No prefix found.")) + raise ValueError("No prefix found.") async def call_alias(self, message: discord.Message, prefix: str, alias: AliasEntry): new_message = copy(message) diff --git a/redbot/cogs/audio/core/events/lavalink.py b/redbot/cogs/audio/core/events/lavalink.py index 876e9c10a..9b94f132c 100644 --- a/redbot/cogs/audio/core/events/lavalink.py +++ b/redbot/cogs/audio/core/events/lavalink.py @@ -6,7 +6,7 @@ from pathlib import Path import discord import lavalink -from redbot.core.i18n import Translator +from redbot.core.i18n import Translator, set_contextual_locales_from_guild from ...errors import DatabaseError, TrackEnqueueError from ..abc import MixinMeta from ..cog_utils import CompositeMetaClass @@ -31,6 +31,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass): guild_id = self.rgetattr(guild, "id", None) if not guild: return + await set_contextual_locales_from_guild(self.bot, guild) current_requester = self.rgetattr(current_track, "requester", None) current_stream = self.rgetattr(current_track, "is_stream", None) current_length = self.rgetattr(current_track, "length", None) diff --git a/redbot/cogs/filter/filter.py b/redbot/cogs/filter/filter.py index 63f8032de..c9a888265 100644 --- a/redbot/cogs/filter/filter.py +++ b/redbot/cogs/filter/filter.py @@ -5,7 +5,7 @@ from typing import Union, Set, Literal from redbot.core import checks, Config, modlog, commands from redbot.core.bot import Red -from redbot.core.i18n import Translator, cog_i18n +from redbot.core.i18n import Translator, cog_i18n, set_contextual_locales_from_guild from redbot.core.utils import AsyncIter from redbot.core.utils.chat_formatting import pagify, humanize_list @@ -396,6 +396,8 @@ class Filter(commands.Cog): if await self.bot.is_automod_immune(message): return + await set_contextual_locales_from_guild(self.bot, message.guild) + await self.check_filter(message) @commands.Cog.listener() @@ -429,6 +431,8 @@ class Filter(commands.Cog): if not guild_data["filter_names"]: return + await set_contextual_locales_from_guild(self.bot, guild) + if await self.filter_hits(member.display_name, member.guild): name_to_use = guild_data["filter_default_name"] diff --git a/redbot/cogs/mod/events.py b/redbot/cogs/mod/events.py index a3a6ca8c4..c05b66f77 100644 --- a/redbot/cogs/mod/events.py +++ b/redbot/cogs/mod/events.py @@ -151,6 +151,9 @@ class Events(MixinMeta): # As are anyone configured to be if await self.bot.is_automod_immune(message): return + + await i18n.set_contextual_locales_from_guild(self.bot, message.guild) + deleted = await self.check_duplicates(message) if not deleted: await self.check_mention_spam(message) diff --git a/redbot/cogs/mutes/mutes.py b/redbot/cogs/mutes/mutes.py index fe6bff31f..1182f1998 100644 --- a/redbot/cogs/mutes/mutes.py +++ b/redbot/cogs/mutes/mutes.py @@ -194,6 +194,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): guild = self.bot.get_guild(g_id) if guild is None or await self.bot.cog_disabled_in_guild(self, guild): continue + await i18n.set_contextual_locales_from_guild(self.bot, guild) for u_id in self._server_mutes[guild.id]: if self._server_mutes[guild.id][u_id]["until"] is None: continue @@ -295,6 +296,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): for guild_id, users in multiple_mutes.items(): guild = self.bot.get_guild(guild_id) + await i18n.set_contextual_locales_from_guild(self.bot, guild) for user, channels in users.items(): if len(channels) > 1: task_name = f"server-unmute-channels-{guild_id}-{user}" @@ -461,6 +463,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): a = set(after.roles) roles_removed = list(b - a) roles_added = list(a - b) + await i18n.set_contextual_locales_from_guild(self.bot, guild) if mute_role in roles_removed: # send modlog case for unmute and remove from cache if guild.id not in self._server_mutes: @@ -511,6 +514,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): """ if await self.bot.cog_disabled_in_guild(self, after.guild): return + await i18n.set_contextual_locales_from_guild(self.bot, after.guild) if after.id in self._channel_mutes: before_perms: Dict[int, Dict[str, Optional[bool]]] = { o.id: {name: attr for name, attr in p} for o, p in before.overwrites.items() @@ -569,6 +573,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass): # user to globally rate limit the bot therefore we are not # going to support re-muting users via channel overwrites return + await i18n.set_contextual_locales_from_guild(self.bot, guild) if guild.id in self._server_mutes: if member.id in self._server_mutes[guild.id]: role = guild.get_role(mute_role) diff --git a/redbot/cogs/reports/reports.py b/redbot/cogs/reports/reports.py index e00c33be2..cb265eab8 100644 --- a/redbot/cogs/reports/reports.py +++ b/redbot/cogs/reports/reports.py @@ -11,7 +11,7 @@ from redbot.core.utils import AsyncIter from redbot.core.utils.chat_formatting import pagify, box from redbot.core.utils.antispam import AntiSpam from redbot.core.bot import Red -from redbot.core.i18n import Translator, cog_i18n +from redbot.core.i18n import Translator, cog_i18n, set_contextual_locales_from_guild from redbot.core.utils.predicates import MessagePredicate from redbot.core.utils.tunnel import Tunnel @@ -346,8 +346,10 @@ class Reports(commands.Cog): if t is None: return + guild = t[0][0] tun = t[1]["tun"] if payload.user_id in [x.id for x in tun.members]: + await set_contextual_locales_from_guild(self.bot, guild) await tun.react_close( uid=payload.user_id, message=_("{closer} has closed the correspondence") ) @@ -365,6 +367,7 @@ class Reports(commands.Cog): to_remove.append(k) continue + await set_contextual_locales_from_guild(self.bot, guild) topic = _("Re: ticket# {ticket_number} in {guild.name}").format( ticket_number=ticket_number, guild=guild ) @@ -376,6 +379,7 @@ class Reports(commands.Cog): for key in to_remove: if tun := self.tunnel_store.pop(key, None): guild, ticket = key + await set_contextual_locales_from_guild(self.bot, guild) await tun["tun"].close_because_disabled( _( "Correspondence about ticket# {ticket_number} in " diff --git a/redbot/cogs/streams/streams.py b/redbot/cogs/streams/streams.py index 8117e63e6..8c25216db 100644 --- a/redbot/cogs/streams/streams.py +++ b/redbot/cogs/streams/streams.py @@ -1,7 +1,7 @@ import discord from redbot.core.bot import Red from redbot.core import checks, commands, Config -from redbot.core.i18n import cog_i18n, Translator +from redbot.core.i18n import cog_i18n, Translator, set_contextual_locales_from_guild from redbot.core.utils._internal_utils import send_to_owners_with_prefix_replaced from redbot.core.utils.chat_formatting import escape, pagify @@ -714,6 +714,9 @@ class Streams(commands.Cog): ignore_reruns = await self.config.guild(channel.guild).ignore_reruns() if ignore_reruns and is_rerun: continue + + await set_contextual_locales_from_guild(self.bot, channel.guild) + mention_str, edited_roles = await self._get_mention_str( channel.guild, channel ) diff --git a/redbot/core/bot.py b/redbot/core/bot.py index 1eb2644d1..b4534b73d 100644 --- a/redbot/core/bot.py +++ b/redbot/core/bot.py @@ -41,14 +41,13 @@ from .data_manager import cog_data_path from .dev_commands import Dev from .events import init_events from .global_checks import init_global_checks - from .settings_caches import ( PrefixManager, IgnoreManager, WhitelistBlacklistManager, DisabledCogCache, + I18nManager, ) - from .rpc import RPCMixin from .utils import common_filters, AsyncIter from .utils._internal_utils import send_to_owners_with_prefix_replaced @@ -142,6 +141,8 @@ class RedBase( disabled_commands=[], autoimmune_ids=[], delete_delay=-1, + locale=None, + regional_format=None, ) self._config.register_channel(embeds=None, ignored=False) @@ -159,6 +160,7 @@ class RedBase( self._disabled_cog_cache = DisabledCogCache(self._config) self._ignored_cache = IgnoreManager(self._config) self._whiteblacklist_cache = WhitelistBlacklistManager(self._config) + self._i18n_cache = I18nManager(self._config) async def prefix_manager(bot, message) -> List[str]: prefixes = await self._prefix_cache.get_prefixes(message.guild) diff --git a/redbot/core/core_commands.py b/redbot/core/core_commands.py index 71260d63d..dca184555 100644 --- a/redbot/core/core_commands.py +++ b/redbot/core/core_commands.py @@ -1569,8 +1569,22 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): mod_role_ids = guild_data["mod_role"] mod_role_names = [r.name for r in guild.roles if r.id in mod_role_ids] mod_roles_str = humanize_list(mod_role_names) if mod_role_names else "Not Set." - guild_settings = _("Admin roles: {admin}\nMod roles: {mod}\n").format( - admin=admin_roles_str, mod=mod_roles_str + + guild_locale = await i18n.get_locale_from_guild(self.bot, ctx.guild) + guild_regional_format = ( + await i18n.get_regional_format_from_guild(self.bot, ctx.guild) or guild_locale + ) + + guild_settings = _( + "Admin roles: {admin}\n" + "Mod roles: {mod}\n" + "Locale: {guild_locale}\n" + "Regional format: {guild_regional_format}\n" + ).format( + admin=admin_roles_str, + mod=mod_roles_str, + guild_locale=guild_locale, + guild_regional_format=guild_regional_format, ) else: guild_settings = "" @@ -1578,7 +1592,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): prefixes = await ctx.bot._prefix_cache.get_prefixes(ctx.guild) global_data = await ctx.bot._config.all() locale = global_data["locale"] - regional_format = global_data["regional_format"] or _("Same as bot's locale") + regional_format = global_data["regional_format"] or locale colour = discord.Colour(global_data["color"]) prefix_string = " ".join(prefixes) @@ -1586,8 +1600,8 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): "{bot_name} Settings:\n\n" "Prefixes: {prefixes}\n" "{guild_settings}" - "Locale: {locale}\n" - "Regional format: {regional_format}\n" + "Global locale: {locale}\n" + "Global regional format: {regional_format}\n" "Default embed colour: {colour}" ).format( bot_name=ctx.bot.user.name, @@ -2018,9 +2032,10 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): @_set.command() @checks.is_owner() - async def locale(self, ctx: commands.Context, language_code: str): + async def globallocale(self, ctx: commands.Context, language_code: str): """ - Changes bot's locale. + Changes the bot's default locale. + This will be used when a server has not set a locale, or in DMs. `` can be any language code with country code included, e.g. `en-US`, `de-DE`, `fr-FR`, `pl-PL`, etc. @@ -2042,12 +2057,51 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): return standardized_locale_name = f"{locale.language}-{locale.territory}" i18n.set_locale(standardized_locale_name) - await ctx.bot._config.locale.set(standardized_locale_name) + await self.bot._i18n_cache.set_locale(None, standardized_locale_name) + await i18n.set_contextual_locales_from_guild(self.bot, ctx.guild) + await ctx.send(_("Global locale has been set.")) + + @_set.command() + @commands.guild_only() + @checks.guildowner_or_permissions(manage_guild=True) + async def locale(self, ctx: commands.Context, language_code: str): + """ + Changes the bot's locale in this server. + + `` can be any language code with country code included, + e.g. `en-US`, `de-DE`, `fr-FR`, `pl-PL`, etc. + + Go to Red's Crowdin page to see locales that are available with translations: + https://translate.discord.red + + Use "default" to return to the bot's default set language. + To reset to English, use "en-US". + """ + if language_code.lower() == "default": + global_locale = await self.bot._config.locale() + i18n.set_contextual_locale(global_locale) + await self.bot._i18n_cache.set_locale(ctx.guild, None) + await ctx.send(_("Locale has been set to the default.")) + return + try: + locale = BabelLocale.parse(language_code, sep="-") + except (ValueError, UnknownLocaleError): + await ctx.send(_("Invalid language code. Use format: `en-US`")) + return + if locale.territory is None: + await ctx.send( + _("Invalid format - language code has to include country code, e.g. `en-US`") + ) + return + standardized_locale_name = f"{locale.language}-{locale.territory}" + i18n.set_contextual_locale(standardized_locale_name) + await self.bot._i18n_cache.set_locale(ctx.guild, standardized_locale_name) await ctx.send(_("Locale has been set.")) - @_set.command(aliases=["region"]) + @_set.command(aliases=["globalregion"]) + @commands.guild_only() @checks.is_owner() - async def regionalformat(self, ctx: commands.Context, language_code: str = None): + async def globalregionalformat(self, ctx: commands.Context, language_code: str = None): """ Changes bot's regional format. This is used for formatting date, time and numbers. @@ -2058,8 +2112,8 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): """ if language_code is None: i18n.set_regional_format(None) - await ctx.bot._config.regional_format.set(None) - await ctx.send(_("Regional formatting will now be based on bot's locale.")) + await self.bot._i18n_cache.set_regional_format(None, None) + await ctx.send(_("Global regional formatting will now be based on bot's locale.")) return try: @@ -2074,7 +2128,45 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic): return standardized_locale_name = f"{locale.language}-{locale.territory}" i18n.set_regional_format(standardized_locale_name) - await ctx.bot._config.regional_format.set(standardized_locale_name) + await self.bot._i18n_cache.set_regional_format(None, standardized_locale_name) + await ctx.send( + _("Global regional formatting will now be based on `{language_code}` locale.").format( + language_code=standardized_locale_name + ) + ) + + @_set.command(aliases=["region"]) + @checks.guildowner_or_permissions(manage_guild=True) + async def regionalformat(self, ctx: commands.Context, language_code: str = None): + """ + Changes bot's regional format in this server. This is used for formatting date, time and numbers. + + `` can be any language code with country code included, + e.g. `en-US`, `de-DE`, `fr-FR`, `pl-PL`, etc. + + Leave `` empty to base regional formatting on bot's locale in this server. + """ + if language_code is None: + i18n.set_contextual_regional_format(None) + await self.bot._i18n_cache.set_regional_format(ctx.guild, None) + await ctx.send( + _("Regional formatting will now be based on bot's locale in this server.") + ) + return + + try: + locale = BabelLocale.parse(language_code, sep="-") + except (ValueError, UnknownLocaleError): + await ctx.send(_("Invalid language code. Use format: `en-US`")) + return + if locale.territory is None: + await ctx.send( + _("Invalid format - language code has to include country code, e.g. `en-US`") + ) + return + standardized_locale_name = f"{locale.language}-{locale.territory}" + i18n.set_contextual_regional_format(standardized_locale_name) + await self.bot._i18n_cache.set_regional_format(ctx.guild, standardized_locale_name) await ctx.send( _("Regional formatting will now be based on `{language_code}` locale.").format( language_code=standardized_locale_name diff --git a/redbot/core/events.py b/redbot/core/events.py index 8d47c5cf9..fda0309e0 100644 --- a/redbot/core/events.py +++ b/redbot/core/events.py @@ -15,7 +15,12 @@ from pkg_resources import DistributionNotFound from redbot.core import data_manager from redbot.core.commands import RedHelpFormatter, HelpSettings -from redbot.core.i18n import Translator +from redbot.core.i18n import ( + Translator, + set_contextual_locale, + set_contextual_regional_format, + set_contextual_locales_from_guild, +) from .utils import AsyncIter from .. import __version__ as red_version, version_info as red_version_info, VersionInfo from . import commands @@ -313,6 +318,8 @@ def init_events(bot, cli_flags): @bot.event async def on_message(message): + await set_contextual_locales_from_guild(bot, message.guild) + await bot.process_commands(message) discord_now = message.created_at if ( diff --git a/redbot/core/i18n.py b/redbot/core/i18n.py index 432688443..46ef35f48 100644 --- a/redbot/core/i18n.py +++ b/redbot/core/i18n.py @@ -1,13 +1,23 @@ +from __future__ import annotations + import contextlib import functools import io import os +import logging +import discord + from pathlib import Path -from typing import Callable, Union, Dict, Optional +from typing import Callable, TYPE_CHECKING, Union, Dict, Optional +from contextvars import ContextVar import babel.localedata from babel.core import Locale +if TYPE_CHECKING: + from redbot.core.bot import Red + + __all__ = [ "get_locale", "set_locale", @@ -16,10 +26,15 @@ __all__ = [ "Translator", "get_babel_locale", "get_babel_regional_format", + "get_locale_from_guild", + "get_regional_format_from_guild", + "set_contextual_locales_from_guild", ] -_current_locale = "en-US" -_current_regional_format = None +log = logging.getLogger("red.i18n") + +_current_locale = ContextVar("_current_locale", default="en-US") +_current_regional_format = ContextVar("_current_regional_format", default=None) WAITING_FOR_MSGID = 1 IN_MSGID = 2 @@ -33,24 +48,33 @@ _translators = [] def get_locale() -> str: - return _current_locale + return str(_current_locale.get()) def set_locale(locale: str) -> None: global _current_locale - _current_locale = locale + _current_locale = ContextVar("_current_locale", default=locale) + reload_locales() + + +def set_contextual_locale(locale: str) -> None: + _current_locale.set(locale) reload_locales() def get_regional_format() -> str: - if _current_regional_format is None: - return _current_locale - return _current_regional_format + if _current_regional_format.get() is None: + return str(_current_locale.get()) + return str(_current_regional_format.get()) def set_regional_format(regional_format: Optional[str]) -> None: global _current_regional_format - _current_regional_format = regional_format + _current_regional_format = ContextVar("_current_regional_format", default=regional_format) + + +def set_contextual_regional_format(regional_format: Optional[str]) -> None: + _current_regional_format.set(regional_format) def reload_locales() -> None: @@ -58,6 +82,64 @@ def reload_locales() -> None: translator.load_translations() +async def get_locale_from_guild(bot: Red, guild: Optional[discord.Guild]) -> str: + """ + Get locale set for the given guild. + + Parameters + ---------- + bot: Red + The bot's instance. + guild: Optional[discord.Guild] + The guild contextual locale is set for. + Use `None` if the context doesn't involve guild. + + Returns + ------- + str + Guild's locale string. + """ + return await bot._i18n_cache.get_locale(guild) + + +async def get_regional_format_from_guild(bot: Red, guild: Optional[discord.Guild]) -> str: + """ + Get regional format for the given guild. + + Parameters + ---------- + bot: Red + The bot's instance. + guild: Optional[discord.Guild] + The guild contextual locale is set for. + Use `None` if the context doesn't involve guild. + + Returns + ------- + str + Guild's locale string. + """ + return await bot._i18n_cache.get_regional_format(guild) + + +async def set_contextual_locales_from_guild(bot: Red, guild: Optional[discord.Guild]) -> None: + """ + Set contextual locales (locale and regional format) for given guild context. + + Parameters + ---------- + bot: Red + The bot's instance. + guild: Optional[discord.Guild] + The guild contextual locale is set for. + Use `None` if the context doesn't involve guild. + """ + locale = await get_locale_from_guild(bot, guild) + regional_format = await get_regional_format_from_guild(bot, guild) + set_contextual_locale(locale) + set_contextual_regional_format(regional_format) + + def _parse(translation_file: io.TextIOWrapper) -> Dict[str, str]: """ Custom gettext parsing of translation files. @@ -78,6 +160,10 @@ def _parse(translation_file: io.TextIOWrapper) -> Dict[str, str]: untranslated = "" translated = "" translations = {} + locale = get_locale() + + translations[locale] = {} + for line in translation_file: line = line.strip() @@ -85,7 +171,7 @@ def _parse(translation_file: io.TextIOWrapper) -> Dict[str, str]: # New msgid if step is IN_MSGSTR and translated: # Store the last translation - translations[_unescape(untranslated)] = _unescape(translated) + translations[locale][_unescape(untranslated)] = _unescape(translated) step = IN_MSGID untranslated = line[len(MSGID) : -1] elif line.startswith('"') and line.endswith('"'): @@ -102,7 +188,7 @@ def _parse(translation_file: io.TextIOWrapper) -> Dict[str, str]: if step is IN_MSGSTR and translated: # Store the final translation - translations[_unescape(untranslated)] = _unescape(translated) + translations[locale][_unescape(untranslated)] = _unescape(translated) return translations @@ -159,8 +245,9 @@ class Translator(Callable[[str], str]): This will look for the string in the translator's :code:`.pot` file, with respect to the current locale. """ + locale = get_locale() try: - return self.translations[untranslated] + return self.translations[locale][untranslated] except KeyError: return untranslated @@ -168,7 +255,16 @@ class Translator(Callable[[str], str]): """ Loads the current translations. """ - self.translations = {} + locale = get_locale() + + if locale.lower() == "en-us": + # Red is written in en-US, no point in loading it + return + if locale in self.translations: + # Locales cannot be loaded twice as they have an entry in + # self.translations + return + locale_path = get_locale_path(self.cog_folder, "po") with contextlib.suppress(IOError, FileNotFoundError): with locale_path.open(encoding="utf-8") as file: diff --git a/redbot/core/modlog.py b/redbot/core/modlog.py index 77c6aed4f..ba9f3fb5a 100644 --- a/redbot/core/modlog.py +++ b/redbot/core/modlog.py @@ -15,7 +15,7 @@ from .utils.common_filters import ( filter_urls, escape_spoilers, ) -from .i18n import Translator +from .i18n import Translator, set_contextual_locales_from_guild from .generic_casetypes import all_generics @@ -885,6 +885,7 @@ async def create_case( await _config.custom(_CASES, str(guild.id), str(next_case_number)).set(case.to_json()) await _config.guild(guild).latest_case_number.set(next_case_number) + await set_contextual_locales_from_guild(bot, guild) bot.dispatch("modlog_case_create", case) try: mod_channel = await get_modlog_channel(case.guild) diff --git a/redbot/core/settings_caches.py b/redbot/core/settings_caches.py index 4568f066a..e829689be 100644 --- a/redbot/core/settings_caches.py +++ b/redbot/core/settings_caches.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Dict, List, Optional, Union, Set, Iterable, Tuple +from typing import Dict, List, Optional, Union, Set, Iterable, Tuple, overload import asyncio from argparse import Namespace from collections import defaultdict @@ -56,6 +56,93 @@ class PrefixManager: await self._config.guild_from_id(gid).prefix.set(prefixes) +class I18nManager: + def __init__(self, config: Config): + self._config: Config = config + self._guild_locale: Dict[Union[int, None], Union[str, None]] = {} + self._guild_regional_format: Dict[Union[int, None], Union[str, None]] = {} + + async def get_locale(self, guild: Union[discord.Guild, None]) -> str: + """Get the guild locale from the cache""" + # Ensure global locale is in the cache + if None not in self._guild_locale: + global_locale = await self._config.locale() + self._guild_locale[None] = global_locale + + if guild is None: # Not a guild so cannot support guild locale + # Return the bot's globally set locale if its None on a guild scope. + return self._guild_locale[None] + elif guild.id in self._guild_locale: # Cached guild + if self._guild_locale[guild.id] is None: + return self._guild_locale[None] + else: + return self._guild_locale[guild.id] + else: # Uncached guild + out = await self._config.guild(guild).locale() # No locale set + if out is None: + self._guild_locale[guild.id] = None + return self._guild_locale[None] + else: + self._guild_locale[guild.id] = out + return out + + @overload + async def set_locale(self, guild: None, locale: str): + ... + + @overload + async def set_locale(self, guild: discord.Guild, locale: Union[str, None]): + ... + + async def set_locale( + self, guild: Union[discord.Guild, None], locale: Union[str, None] + ) -> None: + """Set the locale in the config and cache""" + if guild is None: + if locale is None: + # this method should never be called like this + raise ValueError("Global locale can't be None!") + self._guild_locale[None] = locale + await self._config.locale.set(locale) + return + self._guild_locale[guild.id] = locale + await self._config.guild(guild).locale.set(locale) + + async def get_regional_format(self, guild: Union[discord.Guild, None]) -> Optional[str]: + """Get the regional format from the cache""" + # Ensure global locale is in the cache + if None not in self._guild_regional_format: + global_regional_format = await self._config.regional_format() + self._guild_regional_format[None] = global_regional_format + + if guild is None: # Not a guild so cannot support guild locale + return self._guild_regional_format[None] + elif guild.id in self._guild_regional_format: # Cached guild + if self._guild_regional_format[guild.id] is None: + return self._guild_regional_format[None] + else: + return self._guild_regional_format[guild.id] + else: # Uncached guild + out = await self._config.guild(guild).regional_format() # No locale set + if out is None: + self._guild_regional_format[guild.id] = None + return self._guild_regional_format[None] + else: # Not cached, got a custom regional format. + self._guild_regional_format[guild.id] = out + return out + + async def set_regional_format( + self, guild: Union[discord.Guild, None], regional_format: Union[str, None] + ) -> None: + """Set the regional format in the config and cache""" + if guild is None: + self._guild_regional_format[None] = regional_format + await self._config.regional_format.set(regional_format) + return + self._guild_regional_format[guild.id] = regional_format + await self._config.guild(guild).regional_format.set(regional_format) + + class IgnoreManager: def __init__(self, config: Config): self._config: Config = config