Skip to content

Commit

Permalink
Document and clarify publicity of rarely used i18n APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
Jackenmen committed May 8, 2023
1 parent 41d89c7 commit 0d2e131
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 77 deletions.
1 change: 1 addition & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,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 @@ -1140,9 +1140,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 @@ -3395,17 +3395,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 @@ -3438,17 +3431,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 @@ -3494,23 +3480,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 @@ -3545,17 +3524,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
95 changes: 59 additions & 36 deletions redbot/core/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,23 @@

from pathlib import Path
from typing import Callable, TYPE_CHECKING, Union, Dict, Optional
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",
"get_locale_from_guild",
Expand All @@ -28,13 +35,10 @@
"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 +47,6 @@
MSGID = 'msgid "'
MSGSTR = 'msgstr "'

_translators = []


def get_locale() -> str:
"""
Expand All @@ -55,18 +57,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 +69,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 +174,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 +239,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 +273,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 +303,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

0 comments on commit 0d2e131

Please sign in to comment.