mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-05 18:58:53 -05:00
Split public and private i18n APIs (#6022)
This commit is contained in:
parent
8b1daf1ad0
commit
a0c1713e78
1
.github/labeler.yml
vendored
1
.github/labeler.yml
vendored
@ -216,6 +216,7 @@
|
|||||||
- redbot/core/commands/help.py
|
- redbot/core/commands/help.py
|
||||||
"Category: Core - i18n":
|
"Category: Core - i18n":
|
||||||
# Source
|
# Source
|
||||||
|
- redbot/core/_i18n.py
|
||||||
- redbot/core/i18n.py
|
- redbot/core/i18n.py
|
||||||
# Locale files
|
# Locale files
|
||||||
- redbot/**/locales/*
|
- redbot/**/locales/*
|
||||||
|
|||||||
79
redbot/core/_i18n.py
Normal file
79
redbot/core/_i18n.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from contextvars import ContextVar
|
||||||
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
|
from babel.core import Locale, UnknownLocaleError
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from redbot.core.i18n import Translator
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"current_locale",
|
||||||
|
"current_locale_default",
|
||||||
|
"current_regional_format",
|
||||||
|
"current_regional_format_default",
|
||||||
|
"translators",
|
||||||
|
"set_global_locale",
|
||||||
|
"set_global_regional_format",
|
||||||
|
"set_contextual_locale",
|
||||||
|
"set_contextual_regional_format",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
current_locale = ContextVar("current_locale")
|
||||||
|
current_locale_default = "en-US"
|
||||||
|
current_regional_format = ContextVar("current_regional_format")
|
||||||
|
current_regional_format_default = None
|
||||||
|
|
||||||
|
translators: List[Translator] = []
|
||||||
|
|
||||||
|
|
||||||
|
def _reload_locales() -> None:
|
||||||
|
for translator in translators:
|
||||||
|
translator.load_translations()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_standardized_locale_name(language_code: str) -> str:
|
||||||
|
try:
|
||||||
|
locale = Locale.parse(language_code, sep="-")
|
||||||
|
except (ValueError, UnknownLocaleError):
|
||||||
|
raise ValueError("Invalid language code. Use format: `en-US`")
|
||||||
|
if locale.territory is None:
|
||||||
|
raise ValueError(
|
||||||
|
"Invalid format - language code has to include country code, e.g. `en-US`"
|
||||||
|
)
|
||||||
|
return f"{locale.language}-{locale.territory}"
|
||||||
|
|
||||||
|
|
||||||
|
def set_global_locale(language_code: str, /) -> str:
|
||||||
|
global current_locale_default
|
||||||
|
current_locale_default = _get_standardized_locale_name(language_code)
|
||||||
|
_reload_locales()
|
||||||
|
return current_locale_default
|
||||||
|
|
||||||
|
|
||||||
|
def set_global_regional_format(language_code: Optional[str], /) -> Optional[str]:
|
||||||
|
global current_regional_format_default
|
||||||
|
if language_code is not None:
|
||||||
|
language_code = _get_standardized_locale_name(language_code)
|
||||||
|
current_regional_format_default = language_code
|
||||||
|
return language_code
|
||||||
|
|
||||||
|
|
||||||
|
def set_contextual_locale(language_code: str, /, verify_language_code: bool = False) -> str:
|
||||||
|
if verify_language_code:
|
||||||
|
language_code = _get_standardized_locale_name(language_code)
|
||||||
|
current_locale.set(language_code)
|
||||||
|
_reload_locales()
|
||||||
|
return language_code
|
||||||
|
|
||||||
|
|
||||||
|
def set_contextual_regional_format(
|
||||||
|
language_code: str, /, verify_language_code: bool = False
|
||||||
|
) -> str:
|
||||||
|
if verify_language_code and language_code is not None:
|
||||||
|
language_code = _get_standardized_locale_name(language_code)
|
||||||
|
current_regional_format.set(language_code)
|
||||||
|
return language_code
|
||||||
@ -37,7 +37,7 @@ import discord
|
|||||||
from discord.ext import commands as dpy_commands
|
from discord.ext import commands as dpy_commands
|
||||||
from discord.ext.commands import when_mentioned_or
|
from discord.ext.commands import when_mentioned_or
|
||||||
|
|
||||||
from . import Config, i18n, app_commands, commands, errors, _drivers, modlog, bank
|
from . import Config, _i18n, i18n, app_commands, commands, errors, _drivers, modlog, bank
|
||||||
from ._cli import ExitCodes
|
from ._cli import ExitCodes
|
||||||
from ._cog_manager import CogManager, CogManagerUI
|
from ._cog_manager import CogManager, CogManagerUI
|
||||||
from .core_commands import Core
|
from .core_commands import Core
|
||||||
@ -1145,9 +1145,9 @@ class Red(
|
|||||||
self.owner_ids.add(self._owner_id_overwrite)
|
self.owner_ids.add(self._owner_id_overwrite)
|
||||||
|
|
||||||
i18n_locale = await self._config.locale()
|
i18n_locale = await self._config.locale()
|
||||||
i18n.set_locale(i18n_locale)
|
_i18n.set_global_locale(i18n_locale)
|
||||||
i18n_regional_format = await self._config.regional_format()
|
i18n_regional_format = await self._config.regional_format()
|
||||||
i18n.set_regional_format(i18n_regional_format)
|
_i18n.set_global_regional_format(i18n_regional_format)
|
||||||
|
|
||||||
async def _pre_connect(self) -> None:
|
async def _pre_connect(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -38,7 +38,6 @@ from typing import (
|
|||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import discord
|
import discord
|
||||||
from babel import Locale as BabelLocale, UnknownLocaleError
|
|
||||||
from redbot.core.data_manager import storage_type
|
from redbot.core.data_manager import storage_type
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
@ -46,6 +45,7 @@ from . import (
|
|||||||
version_info as red_version_info,
|
version_info as red_version_info,
|
||||||
commands,
|
commands,
|
||||||
errors,
|
errors,
|
||||||
|
_i18n,
|
||||||
i18n,
|
i18n,
|
||||||
bank,
|
bank,
|
||||||
modlog,
|
modlog,
|
||||||
@ -3522,17 +3522,10 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
|
|||||||
- `<language_code>` - The default locale to use for the bot. This can be any language code with country code included.
|
- `<language_code>` - The default locale to use for the bot. This can be any language code with country code included.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
locale = BabelLocale.parse(language_code, sep="-")
|
standardized_locale_name = _i18n.set_global_locale(language_code)
|
||||||
except (ValueError, UnknownLocaleError):
|
except ValueError:
|
||||||
await ctx.send(_("Invalid language code. Use format: `en-US`"))
|
await ctx.send(_("Invalid language code. Use format: `en-US`"))
|
||||||
return
|
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_locale(standardized_locale_name)
|
|
||||||
await self.bot._i18n_cache.set_locale(None, 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 i18n.set_contextual_locales_from_guild(self.bot, ctx.guild)
|
||||||
await ctx.send(_("Global locale has been set."))
|
await ctx.send(_("Global locale has been set."))
|
||||||
@ -3565,17 +3558,10 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
|
|||||||
await ctx.send(_("Locale has been set to the default."))
|
await ctx.send(_("Locale has been set to the default."))
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
locale = BabelLocale.parse(language_code, sep="-")
|
standardized_locale_name = i18n.set_contextual_locale(language_code)
|
||||||
except (ValueError, UnknownLocaleError):
|
except ValueError:
|
||||||
await ctx.send(_("Invalid language code. Use format: `en-US`"))
|
await ctx.send(_("Invalid language code. Use format: `en-US`"))
|
||||||
return
|
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 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."))
|
||||||
|
|
||||||
@ -3621,23 +3607,16 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
|
|||||||
- `[language_code]` - The default region format to use for the bot.
|
- `[language_code]` - The default region format to use for the bot.
|
||||||
"""
|
"""
|
||||||
if language_code.lower() == "reset":
|
if language_code.lower() == "reset":
|
||||||
i18n.set_regional_format(None)
|
_i18n.set_global_regional_format(None)
|
||||||
await self.bot._i18n_cache.set_regional_format(None, None)
|
await self.bot._i18n_cache.set_regional_format(None, None)
|
||||||
await ctx.send(_("Global 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:
|
||||||
locale = BabelLocale.parse(language_code, sep="-")
|
standardized_locale_name = _i18n.set_global_regional_format(language_code)
|
||||||
except (ValueError, UnknownLocaleError):
|
except ValueError:
|
||||||
await ctx.send(_("Invalid language code. Use format: `en-US`"))
|
await ctx.send(_("Invalid language code. Use format: `en-US`"))
|
||||||
return
|
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_regional_format(standardized_locale_name)
|
|
||||||
await self.bot._i18n_cache.set_regional_format(None, standardized_locale_name)
|
await self.bot._i18n_cache.set_regional_format(None, standardized_locale_name)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Global regional formatting will now be based on `{language_code}` locale.").format(
|
_("Global regional formatting will now be based on `{language_code}` locale.").format(
|
||||||
@ -3672,17 +3651,10 @@ class Core(commands.commands._RuleDropper, commands.Cog, CoreLogic):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
locale = BabelLocale.parse(language_code, sep="-")
|
standardized_locale_name = i18n.set_contextual_regional_format(language_code)
|
||||||
except (ValueError, UnknownLocaleError):
|
except ValueError:
|
||||||
await ctx.send(_("Invalid language code. Use format: `en-US`"))
|
await ctx.send(_("Invalid language code. Use format: `en-US`"))
|
||||||
return
|
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 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(
|
||||||
|
|||||||
@ -9,18 +9,27 @@ import discord
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable, TYPE_CHECKING, Union, Dict, Optional, TypeVar
|
from typing import Callable, TYPE_CHECKING, Union, Dict, Optional, TypeVar
|
||||||
from contextvars import ContextVar
|
|
||||||
|
|
||||||
import babel.localedata
|
import babel.localedata
|
||||||
from babel.core import Locale
|
from babel.core import Locale
|
||||||
|
|
||||||
|
from redbot.core import _i18n
|
||||||
|
from redbot.core._i18n import (
|
||||||
|
current_locale as _current_locale,
|
||||||
|
current_regional_format as _current_regional_format,
|
||||||
|
set_contextual_locale as _set_contextual_locale,
|
||||||
|
set_contextual_regional_format as _set_contextual_regional_format,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = (
|
||||||
"get_locale",
|
"get_locale",
|
||||||
"get_regional_format",
|
"get_regional_format",
|
||||||
|
"set_contextual_locale",
|
||||||
|
"set_contextual_regional_format",
|
||||||
"get_locale_from_guild",
|
"get_locale_from_guild",
|
||||||
"get_regional_format_from_guild",
|
"get_regional_format_from_guild",
|
||||||
"set_contextual_locales_from_guild",
|
"set_contextual_locales_from_guild",
|
||||||
@ -28,13 +37,10 @@ __all__ = [
|
|||||||
"get_babel_locale",
|
"get_babel_locale",
|
||||||
"get_babel_regional_format",
|
"get_babel_regional_format",
|
||||||
"cog_i18n",
|
"cog_i18n",
|
||||||
]
|
)
|
||||||
|
|
||||||
log = logging.getLogger("red.i18n")
|
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
|
WAITING_FOR_MSGID = 1
|
||||||
IN_MSGID = 2
|
IN_MSGID = 2
|
||||||
WAITING_FOR_MSGSTR = 3
|
WAITING_FOR_MSGSTR = 3
|
||||||
@ -43,8 +49,6 @@ IN_MSGSTR = 4
|
|||||||
MSGID = 'msgid "'
|
MSGID = 'msgid "'
|
||||||
MSGSTR = 'msgstr "'
|
MSGSTR = 'msgstr "'
|
||||||
|
|
||||||
_translators = []
|
|
||||||
|
|
||||||
|
|
||||||
def get_locale() -> str:
|
def get_locale() -> str:
|
||||||
"""
|
"""
|
||||||
@ -55,18 +59,7 @@ def get_locale() -> str:
|
|||||||
str
|
str
|
||||||
Current locale's language code with country code included, e.g. "en-US".
|
Current locale's language code with country code included, e.g. "en-US".
|
||||||
"""
|
"""
|
||||||
return str(_current_locale.get())
|
return _current_locale.get(_i18n.current_locale_default)
|
||||||
|
|
||||||
|
|
||||||
def set_locale(locale: str) -> None:
|
|
||||||
global _current_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:
|
def get_regional_format() -> str:
|
||||||
@ -78,23 +71,55 @@ def get_regional_format() -> str:
|
|||||||
str
|
str
|
||||||
Current regional format's language code with country code included, e.g. "en-US".
|
Current regional format's language code with country code included, e.g. "en-US".
|
||||||
"""
|
"""
|
||||||
if _current_regional_format.get() is None:
|
regional_format = _current_regional_format.get(_i18n.current_regional_format_default)
|
||||||
return str(_current_locale.get())
|
if regional_format is None:
|
||||||
return str(_current_regional_format.get())
|
return _current_locale.get(_i18n.current_locale_default)
|
||||||
|
return regional_format
|
||||||
|
|
||||||
|
|
||||||
def set_regional_format(regional_format: Optional[str]) -> None:
|
def set_contextual_locale(language_code: str, /) -> str:
|
||||||
global _current_regional_format
|
"""
|
||||||
_current_regional_format = ContextVar("_current_regional_format", default=regional_format)
|
Set contextual locale (without regional format) to the given value.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
language_code: str
|
||||||
|
Locale's language code with country code included, e.g. "en-US".
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
Standardized locale name.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
ValueError
|
||||||
|
Language code is invalid.
|
||||||
|
"""
|
||||||
|
return _set_contextual_locale(language_code, verify_language_code=True)
|
||||||
|
|
||||||
|
|
||||||
def set_contextual_regional_format(regional_format: Optional[str]) -> None:
|
def set_contextual_regional_format(language_code: Optional[str], /) -> Optional[str]:
|
||||||
_current_regional_format.set(regional_format)
|
"""
|
||||||
|
Set contextual regional format to the given value.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
language_code: str, optional
|
||||||
|
Contextual regional's language code with country code included, e.g. "en-US"
|
||||||
|
or ``None`` if regional format should inherit the contextual locale's value.
|
||||||
|
|
||||||
def reload_locales() -> None:
|
Returns
|
||||||
for translator in _translators:
|
-------
|
||||||
translator.load_translations()
|
str
|
||||||
|
Standardized locale name or ``None`` if ``None`` was passed.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
ValueError
|
||||||
|
Language code is invalid.
|
||||||
|
"""
|
||||||
|
return _set_contextual_regional_format(language_code, verify_language_code=True)
|
||||||
|
|
||||||
|
|
||||||
async def get_locale_from_guild(bot: Red, guild: Optional[discord.Guild]) -> str:
|
async def get_locale_from_guild(bot: Red, guild: Optional[discord.Guild]) -> str:
|
||||||
@ -151,8 +176,8 @@ async def set_contextual_locales_from_guild(bot: Red, guild: Optional[discord.Gu
|
|||||||
"""
|
"""
|
||||||
locale = await get_locale_from_guild(bot, guild)
|
locale = await get_locale_from_guild(bot, guild)
|
||||||
regional_format = await get_regional_format_from_guild(bot, guild)
|
regional_format = await get_regional_format_from_guild(bot, guild)
|
||||||
set_contextual_locale(locale)
|
_set_contextual_locale(locale)
|
||||||
set_contextual_regional_format(regional_format)
|
_set_contextual_regional_format(regional_format)
|
||||||
|
|
||||||
|
|
||||||
def _parse(translation_file: io.TextIOWrapper) -> Dict[str, str]:
|
def _parse(translation_file: io.TextIOWrapper) -> Dict[str, str]:
|
||||||
@ -216,7 +241,7 @@ def _unescape(string):
|
|||||||
return string
|
return string
|
||||||
|
|
||||||
|
|
||||||
def get_locale_path(cog_folder: Path, extension: str) -> Path:
|
def _get_locale_path(cog_folder: Path, extension: str) -> Path:
|
||||||
"""
|
"""
|
||||||
Gets the folder path containing localization files.
|
Gets the folder path containing localization files.
|
||||||
|
|
||||||
@ -250,7 +275,7 @@ class Translator(Callable[[str], str]):
|
|||||||
self.cog_name = name
|
self.cog_name = name
|
||||||
self.translations = {}
|
self.translations = {}
|
||||||
|
|
||||||
_translators.append(self)
|
_i18n.translators.append(self)
|
||||||
|
|
||||||
self.load_translations()
|
self.load_translations()
|
||||||
|
|
||||||
@ -280,7 +305,7 @@ class Translator(Callable[[str], str]):
|
|||||||
# self.translations
|
# self.translations
|
||||||
return
|
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:
|
||||||
self._parse(file)
|
self._parse(file)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user