[Utils] Add humanize_number() function to chat formatting (#2836)

This adds babel as a dependency, and also includes `redbot.core.i18n.get_babel_locale()`
This commit is contained in:
Draper
2019-08-27 23:44:52 +01:00
committed by Toby Harradine
parent 6c3a3fea66
commit 3c1b6ae4cf
15 changed files with 238 additions and 80 deletions

View File

@@ -5,6 +5,7 @@ from functools import wraps
import discord
from redbot.core.utils.chat_formatting import humanize_number
from . import Config, errors, commands
from .i18n import Translator
@@ -237,11 +238,20 @@ async def withdraw_credits(member: discord.Member, amount: int) -> int:
if not isinstance(amount, int):
raise TypeError("Withdrawal amount must be of type int, not {}.".format(type(amount)))
if _invalid_amount(amount):
raise ValueError("Invalid withdrawal amount {} < 0".format(amount))
raise ValueError(
"Invalid withdrawal amount {} < 0".format(
humanize_number(amount, override_locale="en_US")
)
)
bal = await get_balance(member)
if amount > bal:
raise ValueError("Insufficient funds {} > {}".format(amount, bal))
raise ValueError(
"Insufficient funds {} > {}".format(
humanize_number(amount, override_locale="en_US"),
humanize_number(bal, override_locale="en_US"),
)
)
return await set_balance(member, bal - amount)
@@ -272,7 +282,11 @@ async def deposit_credits(member: discord.Member, amount: int) -> int:
if not isinstance(amount, int):
raise TypeError("Deposit amount must be of type int, not {}.".format(type(amount)))
if _invalid_amount(amount):
raise ValueError("Invalid deposit amount {} <= 0".format(amount))
raise ValueError(
"Invalid deposit amount {} <= 0".format(
humanize_number(amount, override_locale="en_US")
)
)
bal = await get_balance(member)
return await set_balance(member, amount + bal)
@@ -309,7 +323,11 @@ async def transfer_credits(from_: discord.Member, to: discord.Member, amount: in
if not isinstance(amount, int):
raise TypeError("Transfer amount must be of type int, not {}.".format(type(amount)))
if _invalid_amount(amount):
raise ValueError("Invalid transfer amount {} <= 0".format(amount))
raise ValueError(
"Invalid transfer amount {} <= 0".format(
humanize_number(amount, override_locale="en_US")
)
)
if await get_balance(to) + amount > MAX_BALANCE:
currency = await get_currency_name(to.guild)
@@ -727,7 +745,7 @@ def cost(amount: int):
credits_name = await get_currency_name(context.guild)
raise commands.UserFeedbackCheckFailure(
_("You need at least {cost} {currency} to use this command.").format(
cost=amount, currency=credits_name
cost=humanize_number(amount), currency=credits_name
)
)
else:

View File

@@ -2,6 +2,7 @@ import importlib.machinery
import discord
from redbot.core.utils.chat_formatting import humanize_number
from .i18n import Translator
_ = Translator(__name__, __file__)
@@ -45,8 +46,8 @@ class BalanceTooHigh(BankError, OverflowError):
self.currency_name = currency_name
def __str__(self) -> str:
return _("{user}'s balance cannot rise above {max:,} {currency}.").format(
user=self.user, max=self.max_balance, currency=self.currency_name
return _("{user}'s balance cannot rise above max {currency}.").format(
user=self.user, max=humanize_number(self.max_balance), currency=self.currency_name
)

View File

@@ -1,10 +1,21 @@
import contextlib
import functools
import io
import os
from pathlib import Path
from typing import Callable, Union, Dict
from typing import Callable, Union, Dict, Optional
__all__ = ["get_locale", "set_locale", "reload_locales", "cog_i18n", "Translator"]
import babel.localedata
from babel.core import Locale
__all__ = [
"get_locale",
"set_locale",
"reload_locales",
"cog_i18n",
"Translator",
"get_babel_locale",
]
_current_locale = "en-US"
@@ -160,6 +171,44 @@ class Translator(Callable[[str], str]):
self.translations[untranslated] = translated
@functools.lru_cache()
def _get_babel_locale(red_locale: str) -> babel.core.Locale:
supported_locales = babel.localedata.locale_identifiers()
try: # Handles cases where red_locale is already Babel supported
babel_locale = Locale(*babel.parse_locale(red_locale))
except (ValueError, babel.core.UnknownLocaleError):
try:
babel_locale = Locale(*babel.parse_locale(red_locale, sep="-"))
except (ValueError, babel.core.UnknownLocaleError):
# ValueError is Raised by `parse_locale` when an invalid Locale is given to it
# Lets handle it silently and default to "en_US"
try:
# Try to find a babel locale that's close to the one used by red
babel_locale = Locale(Locale.negotiate([red_locale], supported_locales, sep="-"))
except (ValueError, TypeError, babel.core.UnknownLocaleError):
# If we fail to get a close match we will then default to "en_US"
babel_locale = Locale("en", "US")
return babel_locale
def get_babel_locale(locale: Optional[str] = None) -> babel.core.Locale:
"""Function to convert a locale to a ``babel.core.Locale``.
Parameters
----------
locale : Optional[str]
The locale to convert, if not specified it defaults to the bot's locale.
Returns
-------
babel.core.Locale
The babel locale object.
"""
if locale is None:
locale = get_locale()
return _get_babel_locale(locale)
# This import to be down here to avoid circular import issues.
# This will be cleaned up at a later date
# noinspection PyPep8

View File

@@ -1,11 +1,13 @@
import itertools
import datetime
from typing import Sequence, Iterator, List, Optional
from typing import Sequence, Iterator, List, Optional, Union
from io import BytesIO
import discord
from redbot.core.i18n import Translator
import discord
from babel.numbers import format_decimal
from redbot.core.i18n import Translator, get_babel_locale
_ = Translator("UtilsChatFormatting", __file__)
@@ -432,6 +434,25 @@ def humanize_timedelta(
return ", ".join(strings)
def humanize_number(val: Union[int, float], override_locale=None) -> str:
"""
Convert an int or float to a str with digit separators based on bot locale
Parameters
----------
val : Union[int, float]
The int/float to be formatted.
override_locale: Optional[str]
A value to override the bots locale.
Returns
-------
str
locale aware formatted number.
"""
return format_decimal(val, locale=get_babel_locale(override_locale))
def text_to_file(
text: str, filename: str = "file.txt", *, spoiler: bool = False, encoding: str = "utf-8"
):