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:
|
for p in prefixes:
|
||||||
if content.startswith(p):
|
if content.startswith(p):
|
||||||
return 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):
|
async def call_alias(self, message: discord.Message, prefix: str, alias: AliasEntry):
|
||||||
new_message = copy(message)
|
new_message = copy(message)
|
||||||
|
|||||||
@ -6,7 +6,7 @@ from pathlib import Path
|
|||||||
import discord
|
import discord
|
||||||
import lavalink
|
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 ...errors import DatabaseError, TrackEnqueueError
|
||||||
from ..abc import MixinMeta
|
from ..abc import MixinMeta
|
||||||
from ..cog_utils import CompositeMetaClass
|
from ..cog_utils import CompositeMetaClass
|
||||||
@ -31,6 +31,7 @@ class LavalinkEvents(MixinMeta, metaclass=CompositeMetaClass):
|
|||||||
guild_id = self.rgetattr(guild, "id", None)
|
guild_id = self.rgetattr(guild, "id", None)
|
||||||
if not guild:
|
if not guild:
|
||||||
return
|
return
|
||||||
|
await set_contextual_locales_from_guild(self.bot, guild)
|
||||||
current_requester = self.rgetattr(current_track, "requester", None)
|
current_requester = self.rgetattr(current_track, "requester", None)
|
||||||
current_stream = self.rgetattr(current_track, "is_stream", None)
|
current_stream = self.rgetattr(current_track, "is_stream", None)
|
||||||
current_length = self.rgetattr(current_track, "length", 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 import checks, Config, modlog, commands
|
||||||
from redbot.core.bot import Red
|
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 import AsyncIter
|
||||||
from redbot.core.utils.chat_formatting import pagify, humanize_list
|
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):
|
if await self.bot.is_automod_immune(message):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
await set_contextual_locales_from_guild(self.bot, message.guild)
|
||||||
|
|
||||||
await self.check_filter(message)
|
await self.check_filter(message)
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
@ -429,6 +431,8 @@ class Filter(commands.Cog):
|
|||||||
if not guild_data["filter_names"]:
|
if not guild_data["filter_names"]:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
await set_contextual_locales_from_guild(self.bot, guild)
|
||||||
|
|
||||||
if await self.filter_hits(member.display_name, member.guild):
|
if await self.filter_hits(member.display_name, member.guild):
|
||||||
|
|
||||||
name_to_use = guild_data["filter_default_name"]
|
name_to_use = guild_data["filter_default_name"]
|
||||||
|
|||||||
@ -151,6 +151,9 @@ class Events(MixinMeta):
|
|||||||
# As are anyone configured to be
|
# As are anyone configured to be
|
||||||
if await self.bot.is_automod_immune(message):
|
if await self.bot.is_automod_immune(message):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
await i18n.set_contextual_locales_from_guild(self.bot, message.guild)
|
||||||
|
|
||||||
deleted = await self.check_duplicates(message)
|
deleted = await self.check_duplicates(message)
|
||||||
if not deleted:
|
if not deleted:
|
||||||
await self.check_mention_spam(message)
|
await self.check_mention_spam(message)
|
||||||
|
|||||||
@ -194,6 +194,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
|
|||||||
guild = self.bot.get_guild(g_id)
|
guild = self.bot.get_guild(g_id)
|
||||||
if guild is None or await self.bot.cog_disabled_in_guild(self, guild):
|
if guild is None or await self.bot.cog_disabled_in_guild(self, guild):
|
||||||
continue
|
continue
|
||||||
|
await i18n.set_contextual_locales_from_guild(self.bot, guild)
|
||||||
for u_id in self._server_mutes[guild.id]:
|
for u_id in self._server_mutes[guild.id]:
|
||||||
if self._server_mutes[guild.id][u_id]["until"] is None:
|
if self._server_mutes[guild.id][u_id]["until"] is None:
|
||||||
continue
|
continue
|
||||||
@ -295,6 +296,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
|
|||||||
|
|
||||||
for guild_id, users in multiple_mutes.items():
|
for guild_id, users in multiple_mutes.items():
|
||||||
guild = self.bot.get_guild(guild_id)
|
guild = self.bot.get_guild(guild_id)
|
||||||
|
await i18n.set_contextual_locales_from_guild(self.bot, guild)
|
||||||
for user, channels in users.items():
|
for user, channels in users.items():
|
||||||
if len(channels) > 1:
|
if len(channels) > 1:
|
||||||
task_name = f"server-unmute-channels-{guild_id}-{user}"
|
task_name = f"server-unmute-channels-{guild_id}-{user}"
|
||||||
@ -461,6 +463,7 @@ class Mutes(VoiceMutes, commands.Cog, metaclass=CompositeMetaClass):
|
|||||||
a = set(after.roles)
|
a = set(after.roles)
|
||||||
roles_removed = list(b - a)
|
roles_removed = list(b - a)
|
||||||
roles_added = list(a - b)
|
roles_added = list(a - b)
|
||||||
|
await i18n.set_contextual_locales_from_guild(self.bot, guild)
|
||||||
if mute_role in roles_removed:
|
if mute_role in roles_removed:
|
||||||
# send modlog case for unmute and remove from cache
|
# send modlog case for unmute and remove from cache
|
||||||
if guild.id not in self._server_mutes:
|
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):
|
if await self.bot.cog_disabled_in_guild(self, after.guild):
|
||||||
return
|
return
|
||||||
|
await i18n.set_contextual_locales_from_guild(self.bot, after.guild)
|
||||||
if after.id in self._channel_mutes:
|
if after.id in self._channel_mutes:
|
||||||
before_perms: Dict[int, Dict[str, Optional[bool]]] = {
|
before_perms: Dict[int, Dict[str, Optional[bool]]] = {
|
||||||
o.id: {name: attr for name, attr in p} for o, p in before.overwrites.items()
|
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
|
# user to globally rate limit the bot therefore we are not
|
||||||
# going to support re-muting users via channel overwrites
|
# going to support re-muting users via channel overwrites
|
||||||
return
|
return
|
||||||
|
await i18n.set_contextual_locales_from_guild(self.bot, guild)
|
||||||
if guild.id in self._server_mutes:
|
if guild.id in self._server_mutes:
|
||||||
if member.id in self._server_mutes[guild.id]:
|
if member.id in self._server_mutes[guild.id]:
|
||||||
role = guild.get_role(mute_role)
|
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.chat_formatting import pagify, box
|
||||||
from redbot.core.utils.antispam import AntiSpam
|
from redbot.core.utils.antispam import AntiSpam
|
||||||
from redbot.core.bot import Red
|
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.predicates import MessagePredicate
|
||||||
from redbot.core.utils.tunnel import Tunnel
|
from redbot.core.utils.tunnel import Tunnel
|
||||||
|
|
||||||
@ -346,8 +346,10 @@ class Reports(commands.Cog):
|
|||||||
|
|
||||||
if t is None:
|
if t is None:
|
||||||
return
|
return
|
||||||
|
guild = t[0][0]
|
||||||
tun = t[1]["tun"]
|
tun = t[1]["tun"]
|
||||||
if payload.user_id in [x.id for x in tun.members]:
|
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(
|
await tun.react_close(
|
||||||
uid=payload.user_id, message=_("{closer} has closed the correspondence")
|
uid=payload.user_id, message=_("{closer} has closed the correspondence")
|
||||||
)
|
)
|
||||||
@ -365,6 +367,7 @@ class Reports(commands.Cog):
|
|||||||
to_remove.append(k)
|
to_remove.append(k)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
await set_contextual_locales_from_guild(self.bot, guild)
|
||||||
topic = _("Re: ticket# {ticket_number} in {guild.name}").format(
|
topic = _("Re: ticket# {ticket_number} in {guild.name}").format(
|
||||||
ticket_number=ticket_number, guild=guild
|
ticket_number=ticket_number, guild=guild
|
||||||
)
|
)
|
||||||
@ -376,6 +379,7 @@ class Reports(commands.Cog):
|
|||||||
for key in to_remove:
|
for key in to_remove:
|
||||||
if tun := self.tunnel_store.pop(key, None):
|
if tun := self.tunnel_store.pop(key, None):
|
||||||
guild, ticket = key
|
guild, ticket = key
|
||||||
|
await set_contextual_locales_from_guild(self.bot, guild)
|
||||||
await tun["tun"].close_because_disabled(
|
await tun["tun"].close_because_disabled(
|
||||||
_(
|
_(
|
||||||
"Correspondence about ticket# {ticket_number} in "
|
"Correspondence about ticket# {ticket_number} in "
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import discord
|
import discord
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
from redbot.core import checks, commands, Config
|
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._internal_utils import send_to_owners_with_prefix_replaced
|
||||||
from redbot.core.utils.chat_formatting import escape, pagify
|
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()
|
ignore_reruns = await self.config.guild(channel.guild).ignore_reruns()
|
||||||
if ignore_reruns and is_rerun:
|
if ignore_reruns and is_rerun:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
await set_contextual_locales_from_guild(self.bot, channel.guild)
|
||||||
|
|
||||||
mention_str, edited_roles = await self._get_mention_str(
|
mention_str, edited_roles = await self._get_mention_str(
|
||||||
channel.guild, channel
|
channel.guild, channel
|
||||||
)
|
)
|
||||||
|
|||||||
@ -41,14 +41,13 @@ from .data_manager import cog_data_path
|
|||||||
from .dev_commands import Dev
|
from .dev_commands import Dev
|
||||||
from .events import init_events
|
from .events import init_events
|
||||||
from .global_checks import init_global_checks
|
from .global_checks import init_global_checks
|
||||||
|
|
||||||
from .settings_caches import (
|
from .settings_caches import (
|
||||||
PrefixManager,
|
PrefixManager,
|
||||||
IgnoreManager,
|
IgnoreManager,
|
||||||
WhitelistBlacklistManager,
|
WhitelistBlacklistManager,
|
||||||
DisabledCogCache,
|
DisabledCogCache,
|
||||||
|
I18nManager,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .rpc import RPCMixin
|
from .rpc import RPCMixin
|
||||||
from .utils import common_filters, AsyncIter
|
from .utils import common_filters, AsyncIter
|
||||||
from .utils._internal_utils import send_to_owners_with_prefix_replaced
|
from .utils._internal_utils import send_to_owners_with_prefix_replaced
|
||||||
@ -142,6 +141,8 @@ class RedBase(
|
|||||||
disabled_commands=[],
|
disabled_commands=[],
|
||||||
autoimmune_ids=[],
|
autoimmune_ids=[],
|
||||||
delete_delay=-1,
|
delete_delay=-1,
|
||||||
|
locale=None,
|
||||||
|
regional_format=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._config.register_channel(embeds=None, ignored=False)
|
self._config.register_channel(embeds=None, ignored=False)
|
||||||
@ -159,6 +160,7 @@ class RedBase(
|
|||||||
self._disabled_cog_cache = DisabledCogCache(self._config)
|
self._disabled_cog_cache = DisabledCogCache(self._config)
|
||||||
self._ignored_cache = IgnoreManager(self._config)
|
self._ignored_cache = IgnoreManager(self._config)
|
||||||
self._whiteblacklist_cache = WhitelistBlacklistManager(self._config)
|
self._whiteblacklist_cache = WhitelistBlacklistManager(self._config)
|
||||||
|
self._i18n_cache = I18nManager(self._config)
|
||||||
|
|
||||||
async def prefix_manager(bot, message) -> List[str]:
|
async def prefix_manager(bot, message) -> List[str]:
|
||||||
prefixes = await self._prefix_cache.get_prefixes(message.guild)
|
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_ids = guild_data["mod_role"]
|
||||||
mod_role_names = [r.name for r in guild.roles if r.id in mod_role_ids]
|
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."
|
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:
|
else:
|
||||||
guild_settings = ""
|
guild_settings = ""
|
||||||
@ -1578,7 +1592,7 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
|
|||||||
prefixes = await ctx.bot._prefix_cache.get_prefixes(ctx.guild)
|
prefixes = await ctx.bot._prefix_cache.get_prefixes(ctx.guild)
|
||||||
global_data = await ctx.bot._config.all()
|
global_data = await ctx.bot._config.all()
|
||||||
locale = global_data["locale"]
|
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"])
|
colour = discord.Colour(global_data["color"])
|
||||||
|
|
||||||
prefix_string = " ".join(prefixes)
|
prefix_string = " ".join(prefixes)
|
||||||
@ -1586,8 +1600,8 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
|
|||||||
"{bot_name} Settings:\n\n"
|
"{bot_name} Settings:\n\n"
|
||||||
"Prefixes: {prefixes}\n"
|
"Prefixes: {prefixes}\n"
|
||||||
"{guild_settings}"
|
"{guild_settings}"
|
||||||
"Locale: {locale}\n"
|
"Global locale: {locale}\n"
|
||||||
"Regional format: {regional_format}\n"
|
"Global regional format: {regional_format}\n"
|
||||||
"Default embed colour: {colour}"
|
"Default embed colour: {colour}"
|
||||||
).format(
|
).format(
|
||||||
bot_name=ctx.bot.user.name,
|
bot_name=ctx.bot.user.name,
|
||||||
@ -2018,9 +2032,10 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
|
|||||||
|
|
||||||
@_set.command()
|
@_set.command()
|
||||||
@checks.is_owner()
|
@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,
|
`<language_code>` can be any language code with country code included,
|
||||||
e.g. `en-US`, `de-DE`, `fr-FR`, `pl-PL`, etc.
|
e.g. `en-US`, `de-DE`, `fr-FR`, `pl-PL`, etc.
|
||||||
@ -2042,12 +2057,51 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
|
|||||||
return
|
return
|
||||||
standardized_locale_name = f"{locale.language}-{locale.territory}"
|
standardized_locale_name = f"{locale.language}-{locale.territory}"
|
||||||
i18n.set_locale(standardized_locale_name)
|
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."))
|
await ctx.send(_("Locale has been set."))
|
||||||
|
|
||||||
@_set.command(aliases=["region"])
|
@_set.command(aliases=["globalregion"])
|
||||||
|
@commands.guild_only()
|
||||||
@checks.is_owner()
|
@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.
|
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:
|
if language_code is None:
|
||||||
i18n.set_regional_format(None)
|
i18n.set_regional_format(None)
|
||||||
await ctx.bot._config.regional_format.set(None)
|
await self.bot._i18n_cache.set_regional_format(None, None)
|
||||||
await ctx.send(_("Regional formatting will now be based on bot's locale."))
|
await ctx.send(_("Global regional formatting will now be based on bot's locale."))
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -2074,7 +2128,45 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
|
|||||||
return
|
return
|
||||||
standardized_locale_name = f"{locale.language}-{locale.territory}"
|
standardized_locale_name = f"{locale.language}-{locale.territory}"
|
||||||
i18n.set_regional_format(standardized_locale_name)
|
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(
|
await ctx.send(
|
||||||
_("Regional formatting will now be based on `{language_code}` locale.").format(
|
_("Regional formatting will now be based on `{language_code}` locale.").format(
|
||||||
language_code=standardized_locale_name
|
language_code=standardized_locale_name
|
||||||
|
|||||||
@ -15,7 +15,12 @@ from pkg_resources import DistributionNotFound
|
|||||||
from redbot.core import data_manager
|
from redbot.core import data_manager
|
||||||
|
|
||||||
from redbot.core.commands import RedHelpFormatter, HelpSettings
|
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 .utils import AsyncIter
|
||||||
from .. import __version__ as red_version, version_info as red_version_info, VersionInfo
|
from .. import __version__ as red_version, version_info as red_version_info, VersionInfo
|
||||||
from . import commands
|
from . import commands
|
||||||
@ -313,6 +318,8 @@ def init_events(bot, cli_flags):
|
|||||||
|
|
||||||
@bot.event
|
@bot.event
|
||||||
async def on_message(message):
|
async def on_message(message):
|
||||||
|
await set_contextual_locales_from_guild(bot, message.guild)
|
||||||
|
|
||||||
await bot.process_commands(message)
|
await bot.process_commands(message)
|
||||||
discord_now = message.created_at
|
discord_now = message.created_at
|
||||||
if (
|
if (
|
||||||
|
|||||||
@ -1,13 +1,23 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import functools
|
import functools
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
|
import discord
|
||||||
|
|
||||||
from pathlib import Path
|
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
|
import babel.localedata
|
||||||
from babel.core import Locale
|
from babel.core import Locale
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from redbot.core.bot import Red
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"get_locale",
|
"get_locale",
|
||||||
"set_locale",
|
"set_locale",
|
||||||
@ -16,10 +26,15 @@ __all__ = [
|
|||||||
"Translator",
|
"Translator",
|
||||||
"get_babel_locale",
|
"get_babel_locale",
|
||||||
"get_babel_regional_format",
|
"get_babel_regional_format",
|
||||||
|
"get_locale_from_guild",
|
||||||
|
"get_regional_format_from_guild",
|
||||||
|
"set_contextual_locales_from_guild",
|
||||||
]
|
]
|
||||||
|
|
||||||
_current_locale = "en-US"
|
log = logging.getLogger("red.i18n")
|
||||||
_current_regional_format = None
|
|
||||||
|
_current_locale = ContextVar("_current_locale", default="en-US")
|
||||||
|
_current_regional_format = ContextVar("_current_regional_format", default=None)
|
||||||
|
|
||||||
WAITING_FOR_MSGID = 1
|
WAITING_FOR_MSGID = 1
|
||||||
IN_MSGID = 2
|
IN_MSGID = 2
|
||||||
@ -33,24 +48,33 @@ _translators = []
|
|||||||
|
|
||||||
|
|
||||||
def get_locale() -> str:
|
def get_locale() -> str:
|
||||||
return _current_locale
|
return str(_current_locale.get())
|
||||||
|
|
||||||
|
|
||||||
def set_locale(locale: str) -> None:
|
def set_locale(locale: str) -> None:
|
||||||
global _current_locale
|
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()
|
reload_locales()
|
||||||
|
|
||||||
|
|
||||||
def get_regional_format() -> str:
|
def get_regional_format() -> str:
|
||||||
if _current_regional_format is None:
|
if _current_regional_format.get() is None:
|
||||||
return _current_locale
|
return str(_current_locale.get())
|
||||||
return _current_regional_format
|
return str(_current_regional_format.get())
|
||||||
|
|
||||||
|
|
||||||
def set_regional_format(regional_format: Optional[str]) -> None:
|
def set_regional_format(regional_format: Optional[str]) -> None:
|
||||||
global _current_regional_format
|
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:
|
def reload_locales() -> None:
|
||||||
@ -58,6 +82,64 @@ def reload_locales() -> None:
|
|||||||
translator.load_translations()
|
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]:
|
def _parse(translation_file: io.TextIOWrapper) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Custom gettext parsing of translation files.
|
Custom gettext parsing of translation files.
|
||||||
@ -78,6 +160,10 @@ def _parse(translation_file: io.TextIOWrapper) -> Dict[str, str]:
|
|||||||
untranslated = ""
|
untranslated = ""
|
||||||
translated = ""
|
translated = ""
|
||||||
translations = {}
|
translations = {}
|
||||||
|
locale = get_locale()
|
||||||
|
|
||||||
|
translations[locale] = {}
|
||||||
|
|
||||||
for line in translation_file:
|
for line in translation_file:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
|
|
||||||
@ -85,7 +171,7 @@ def _parse(translation_file: io.TextIOWrapper) -> Dict[str, str]:
|
|||||||
# New msgid
|
# New msgid
|
||||||
if step is IN_MSGSTR and translated:
|
if step is IN_MSGSTR and translated:
|
||||||
# Store the last translation
|
# Store the last translation
|
||||||
translations[_unescape(untranslated)] = _unescape(translated)
|
translations[locale][_unescape(untranslated)] = _unescape(translated)
|
||||||
step = IN_MSGID
|
step = IN_MSGID
|
||||||
untranslated = line[len(MSGID) : -1]
|
untranslated = line[len(MSGID) : -1]
|
||||||
elif line.startswith('"') and line.endswith('"'):
|
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:
|
if step is IN_MSGSTR and translated:
|
||||||
# Store the final translation
|
# Store the final translation
|
||||||
translations[_unescape(untranslated)] = _unescape(translated)
|
translations[locale][_unescape(untranslated)] = _unescape(translated)
|
||||||
return translations
|
return translations
|
||||||
|
|
||||||
|
|
||||||
@ -159,8 +245,9 @@ class Translator(Callable[[str], str]):
|
|||||||
This will look for the string in the translator's :code:`.pot` file,
|
This will look for the string in the translator's :code:`.pot` file,
|
||||||
with respect to the current locale.
|
with respect to the current locale.
|
||||||
"""
|
"""
|
||||||
|
locale = get_locale()
|
||||||
try:
|
try:
|
||||||
return self.translations[untranslated]
|
return self.translations[locale][untranslated]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return untranslated
|
return untranslated
|
||||||
|
|
||||||
@ -168,7 +255,16 @@ class Translator(Callable[[str], str]):
|
|||||||
"""
|
"""
|
||||||
Loads the current translations.
|
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")
|
locale_path = get_locale_path(self.cog_folder, "po")
|
||||||
with contextlib.suppress(IOError, FileNotFoundError):
|
with contextlib.suppress(IOError, FileNotFoundError):
|
||||||
with locale_path.open(encoding="utf-8") as file:
|
with locale_path.open(encoding="utf-8") as file:
|
||||||
|
|||||||
@ -15,7 +15,7 @@ from .utils.common_filters import (
|
|||||||
filter_urls,
|
filter_urls,
|
||||||
escape_spoilers,
|
escape_spoilers,
|
||||||
)
|
)
|
||||||
from .i18n import Translator
|
from .i18n import Translator, set_contextual_locales_from_guild
|
||||||
|
|
||||||
from .generic_casetypes import all_generics
|
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.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 _config.guild(guild).latest_case_number.set(next_case_number)
|
||||||
|
|
||||||
|
await set_contextual_locales_from_guild(bot, guild)
|
||||||
bot.dispatch("modlog_case_create", case)
|
bot.dispatch("modlog_case_create", case)
|
||||||
try:
|
try:
|
||||||
mod_channel = await get_modlog_channel(case.guild)
|
mod_channel = await get_modlog_channel(case.guild)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
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
|
import asyncio
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
@ -56,6 +56,93 @@ class PrefixManager:
|
|||||||
await self._config.guild_from_id(gid).prefix.set(prefixes)
|
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:
|
class IgnoreManager:
|
||||||
def __init__(self, config: Config):
|
def __init__(self, config: Config):
|
||||||
self._config: Config = config
|
self._config: Config = config
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user