Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split public and private i18n APIs #6022

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@
- redbot/core/commands/help.py
"Category: Core - i18n":
# Source
- redbot/core/_i18n.py
- redbot/core/i18n.py
# Locale files
- redbot/**/locales/*
Expand Down
79 changes: 79 additions & 0 deletions redbot/core/_i18n.py
Original file line number Diff line number Diff line change
@@ -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
6 changes: 3 additions & 3 deletions redbot/core/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from discord.ext import commands as dpy_commands
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 ._cog_manager import CogManager, CogManagerUI
from .core_commands import Core
Expand Down Expand Up @@ -1145,9 +1145,9 @@ async def _pre_login(self) -> None:
self.owner_ids.add(self._owner_id_overwrite)

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.set_regional_format(i18n_regional_format)
_i18n.set_global_regional_format(i18n_regional_format)

async def _pre_connect(self) -> None:
"""
Expand Down
48 changes: 10 additions & 38 deletions redbot/core/core_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@

import aiohttp
import discord
from babel import Locale as BabelLocale, UnknownLocaleError
from redbot.core.data_manager import storage_type

from . import (
__version__,
version_info as red_version_info,
commands,
errors,
_i18n,
i18n,
bank,
modlog,
Expand Down Expand Up @@ -3522,17 +3522,10 @@ async def _set_locale_global(self, ctx: commands.Context, language_code: str):
- `<language_code>` - The default locale to use for the bot. This can be any language code with country code included.
"""
try:
locale = BabelLocale.parse(language_code, sep="-")
except (ValueError, UnknownLocaleError):
standardized_locale_name = _i18n.set_global_locale(language_code)
except ValueError:
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_locale(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."))
Expand Down Expand Up @@ -3565,17 +3558,10 @@ async def _set_locale_local(self, ctx: commands.Context, language_code: str):
await ctx.send(_("Locale has been set to the default."))
return
try:
locale = BabelLocale.parse(language_code, sep="-")
except (ValueError, UnknownLocaleError):
standardized_locale_name = i18n.set_contextual_locale(language_code)
except ValueError:
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."))

Expand Down Expand Up @@ -3621,23 +3607,16 @@ async def _set_regional_format_global(self, ctx: commands.Context, language_code
- `[language_code]` - The default region format to use for the bot.
"""
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 ctx.send(_("Global regional formatting will now be based on bot's locale."))
return

try:
locale = BabelLocale.parse(language_code, sep="-")
except (ValueError, UnknownLocaleError):
standardized_locale_name = _i18n.set_global_regional_format(language_code)
except ValueError:
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_regional_format(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(
Expand Down Expand Up @@ -3672,17 +3651,10 @@ async def _set_regional_format_local(self, ctx: commands.Context, language_code:
return

try:
locale = BabelLocale.parse(language_code, sep="-")
except (ValueError, UnknownLocaleError):
standardized_locale_name = i18n.set_contextual_regional_format(language_code)
except ValueError:
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(
Expand Down
97 changes: 61 additions & 36 deletions redbot/core/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,38 @@

from pathlib import Path
from typing import Callable, TYPE_CHECKING, Union, Dict, Optional, TypeVar
from contextvars import ContextVar

import babel.localedata
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:
from redbot.core.bot import Red


__all__ = [
__all__ = (
"get_locale",
"get_regional_format",
"set_contextual_locale",
"set_contextual_regional_format",
"get_locale_from_guild",
"get_regional_format_from_guild",
"set_contextual_locales_from_guild",
"Translator",
"get_babel_locale",
"get_babel_regional_format",
"cog_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
IN_MSGID = 2
WAITING_FOR_MSGSTR = 3
Expand All @@ -43,8 +49,6 @@
MSGID = 'msgid "'
MSGSTR = 'msgstr "'

_translators = []


def get_locale() -> str:
"""
Expand All @@ -55,18 +59,7 @@ def get_locale() -> str:
str
Current locale's language code with country code included, e.g. "en-US".
"""
return str(_current_locale.get())


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()
return _current_locale.get(_i18n.current_locale_default)


def get_regional_format() -> str:
Expand All @@ -78,23 +71,55 @@ def get_regional_format() -> str:
str
Current regional format's language code with country code included, e.g. "en-US".
"""
if _current_regional_format.get() is None:
return str(_current_locale.get())
return str(_current_regional_format.get())
regional_format = _current_regional_format.get(_i18n.current_regional_format_default)
if regional_format is None:
return _current_locale.get(_i18n.current_locale_default)
return regional_format


def set_contextual_locale(language_code: str, /) -> str:
"""
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.

def set_regional_format(regional_format: Optional[str]) -> None:
global _current_regional_format
_current_regional_format = ContextVar("_current_regional_format", default=regional_format)
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:
_current_regional_format.set(regional_format)
def set_contextual_regional_format(language_code: Optional[str], /) -> Optional[str]:
"""
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.

Returns
-------
str
Standardized locale name or ``None`` if ``None`` was passed.

def reload_locales() -> None:
for translator in _translators:
translator.load_translations()
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:
Expand Down Expand Up @@ -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)
regional_format = await get_regional_format_from_guild(bot, guild)
set_contextual_locale(locale)
set_contextual_regional_format(regional_format)
_set_contextual_locale(locale)
_set_contextual_regional_format(regional_format)


def _parse(translation_file: io.TextIOWrapper) -> Dict[str, str]:
Expand Down Expand Up @@ -216,7 +241,7 @@ def _unescape(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.

Expand Down Expand Up @@ -250,7 +275,7 @@ def __init__(self, name: str, file_location: Union[str, Path, os.PathLike]):
self.cog_name = name
self.translations = {}

_translators.append(self)
_i18n.translators.append(self)

self.load_translations()

Expand Down Expand Up @@ -280,7 +305,7 @@ def load_translations(self):
# 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 locale_path.open(encoding="utf-8") as file:
self._parse(file)
Expand Down
Loading