mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 03:08:55 -05:00
[Core] Guild scoped I18n (#3896)
* Guild I18n Never again! * Finish off guild scoped i18n * Black formatting * Added guild only flags. * Fix missing import. * Added listing of guild i18n settings * Added API support * Added API support... properly! * Added API support... for realsies! * Auto-translate create_cases instances You're welcome cog creators! Jack talked me into this! * Fix get_regional_format to actually return properly * Cleanup `set showsettings` * Style pass * Update redbot/core/core_commands.py Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> * Update redbot/core/events.py Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> * Update redbot/core/core_commands.py Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> * Fix missing import * Improve caching * Removal of unneeded function * Fix some naming * IDFK anymore... * Reformat * Update redbot/core/settings_caches.py Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> * Update redbot/core/settings_caches.py Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> * Update redbot/core/settings_caches.py Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> * Remove line number * Fix global sets * Set contextual locale manually where needed * Reports cog is wonderful... * Update redbot/core/core_commands.py Co-authored-by: Draper <27962761+Drapersniper@users.noreply.github.com> * Set contextual locale manually where needed in Mutes cog * s Co-authored-by: jack1142 <6032823+jack1142@users.noreply.github.com> Co-authored-by: Draper <27962761+Drapersniper@users.noreply.github.com>
This commit is contained in:
parent
7bb6e60c52
commit
2413c6abd3
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 "
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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.
|
||||
|
||||
`<language_code>` 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.
|
||||
|
||||
`<language_code>` 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.
|
||||
|
||||
`<language_code>` can be any language code with country code included,
|
||||
e.g. `en-US`, `de-DE`, `fr-FR`, `pl-PL`, etc.
|
||||
|
||||
Leave `<language_code>` 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
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user