[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:
Kowlin
2020-10-26 17:59:11 +01:00
committed by GitHub
parent 7bb6e60c52
commit 2413c6abd3
13 changed files with 341 additions and 36 deletions

View File

@@ -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: