Repository: hikari-py/hikari
Pull request #1090 (2022-03-23 19:09:06)

add hikari.locales.Locale
### Summary

Add `hikari.locales.Locale` enum. (possibly breaking change?)

Not sure where else to put it, tell me if there's a better place for it. Open for feedback :)

### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [x] I have run `nox` and all the pipelines have passed.
- [x] I have made unittests according to the code I have added/modified/deleted.



Patch:
diff --git a/changes/1990.feature.md b/changes/1990.feature.md
new file mode 100644
index 00000000..d8803300
--- /dev/null
+++ b/changes/1990.feature.md
@@ -0,0 +1,1 @@
+Add `hikari.locales.Locale` to help with Discord locale strings.
diff --git a/hikari/__init__.py b/hikari/__init__.py
index 5e2be36f..c3cb1b34 100644
--- a/hikari/__init__.py
+++ b/hikari/__init__.py
@@ -103,6 +103,7 @@
 from hikari.interactions.component_interactions import *
 from hikari.invites import *
 from hikari.iterators import *
+from hikari.locales import *
 from hikari.messages import *
 from hikari.permissions import *
 from hikari.presences import *
diff --git a/hikari/__init__.pyi b/hikari/__init__.pyi
index 262c23f3..e9067103 100644
--- a/hikari/__init__.pyi
+++ b/hikari/__init__.pyi
@@ -76,6 +76,7 @@ from hikari.interactions.command_interactions import *
 from hikari.interactions.component_interactions import *
 from hikari.invites import *
 from hikari.iterators import *
+from hikari.locales import *
 from hikari.messages import *
 from hikari.permissions import *
 from hikari.presences import *
diff --git a/hikari/api/rest.py b/hikari/api/rest.py
index 12dba041..0eb18cb4 100644
--- a/hikari/api/rest.py
+++ b/hikari/api/rest.py
@@ -46,6 +46,7 @@
     from hikari import guilds
     from hikari import invites
     from hikari import iterators
+    from hikari import locales
     from hikari import messages as messages_
     from hikari import permissions as permissions_
     from hikari import sessions
@@ -4209,7 +4210,7 @@ async def edit_guild(
         public_updates_channel: undefined.UndefinedNoneOr[
             snowflakes.SnowflakeishOr[channels_.GuildTextChannel]
         ] = undefined.UNDEFINED,
-        preferred_locale: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+        preferred_locale: undefined.UndefinedOr[typing.Union[str, locales.Locale]] = undefined.UNDEFINED,
         reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
     ) -> guilds.RESTGuild:
         """Edit a guild.
diff --git a/hikari/guilds.py b/hikari/guilds.py
index 3a6e8ac3..c1427102 100644
--- a/hikari/guilds.py
+++ b/hikari/guilds.py
@@ -77,6 +77,7 @@
     from hikari import colours
     from hikari import emojis as emojis_
     from hikari import files
+    from hikari import locales
     from hikari import permissions as permissions_
     from hikari import presences as presences_
     from hikari import voices as voices_
@@ -1656,7 +1657,7 @@ async def edit(
         public_updates_channel: undefined.UndefinedNoneOr[
             snowflakes.SnowflakeishOr[channels_.GuildTextChannel]
         ] = undefined.UNDEFINED,
-        preferred_locale: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+        preferred_locale: undefined.UndefinedOr[typing.Union[str, locales.Locale]] = undefined.UNDEFINED,
         reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
     ) -> RESTGuild:
         """Edits the guild.
@@ -2758,7 +2759,7 @@ class Guild(PartialGuild):
     owner_id: snowflakes.Snowflake = attr.field(eq=False, hash=False, repr=True)
     """The ID of the owner of this guild."""
 
-    preferred_locale: str = attr.field(eq=False, hash=False, repr=False)
+    preferred_locale: typing.Union[str, locales.Locale] = attr.field(eq=False, hash=False, repr=False)
     """The preferred locale to use for this guild.
 
     This can only be change if `GuildFeature.COMMUNITY` is in `Guild.features`
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index 45ad579e..d5c29965 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -43,6 +43,7 @@
 from hikari import files
 from hikari import guilds as guild_models
 from hikari import invites as invite_models
+from hikari import locales
 from hikari import messages as message_models
 from hikari import permissions as permission_models
 from hikari import presences as presence_models
@@ -147,7 +148,7 @@ class _GuildFields:
     banner_hash: typing.Optional[str] = attr.field()
     premium_tier: typing.Union[guild_models.GuildPremiumTier, int] = attr.field()
     premium_subscription_count: typing.Optional[int] = attr.field()
-    preferred_locale: str = attr.field()
+    preferred_locale: typing.Union[str, locales.Locale] = attr.field()
     public_updates_channel_id: typing.Optional[snowflakes.Snowflake] = attr.field()
     nsfw_level: guild_models.GuildNSFWLevel = attr.field()
 
@@ -1401,7 +1402,7 @@ def _set_guild_attributes(self, payload: data_binding.JSONObject) -> _GuildField
             banner_hash=payload["banner"],
             premium_tier=guild_models.GuildPremiumTier(payload["premium_tier"]),
             premium_subscription_count=payload.get("premium_subscription_count"),
-            preferred_locale=payload["preferred_locale"],
+            preferred_locale=locales.Locale(payload["preferred_locale"]),
             public_updates_channel_id=public_updates_channel_id,
             nsfw_level=guild_models.GuildNSFWLevel(payload["nsfw_level"]),
         )
@@ -1992,8 +1993,8 @@ def deserialize_command_interaction(
             id=snowflakes.Snowflake(payload["id"]),
             type=base_interactions.InteractionType(payload["type"]),
             guild_id=guild_id,
-            guild_locale=payload.get("guild_locale", "en-US"),
-            locale=payload["locale"],
+            guild_locale=locales.Locale(payload.get("guild_locale", "en-US")),
+            locale=locales.Locale(payload["locale"]),
             channel_id=snowflakes.Snowflake(payload["channel_id"]),
             member=member,
             user=user,
@@ -2049,8 +2050,8 @@ def deserialize_autocomplete_interaction(
             command_type=commands.CommandType(data_payload.get("type", commands.CommandType.SLASH)),
             options=options,
             resolved=resolved,
-            locale=payload["locale"],
-            guild_locale=payload.get("guild_locale"),
+            locale=locales.Locale(payload["locale"]),
+            guild_locale=locales.Locale(payload.get("guild_locale")),
         )
 
     def deserialize_interaction(self, payload: data_binding.JSONObject) -> base_interactions.PartialInteraction:
@@ -2124,8 +2125,8 @@ def deserialize_component_interaction(
             custom_id=data_payload["custom_id"],
             component_type=message_models.ComponentType(data_payload["component_type"]),
             message=self.deserialize_message(payload["message"]),
-            locale=payload["locale"],
-            guild_locale=payload.get("guild_locale"),
+            locale=locales.Locale(payload["locale"]),
+            guild_locale=locales.Locale(payload.get("guild_locale")),
         )
 
     ##################
@@ -2850,7 +2851,7 @@ def deserialize_template(self, payload: data_binding.JSONObject) -> template_mod
             verification_level=guild_models.GuildVerificationLevel(source_guild_payload["verification_level"]),
             default_message_notifications=default_message_notifications,
             explicit_content_filter=explicit_content_filter,
-            preferred_locale=source_guild_payload["preferred_locale"],
+            preferred_locale=locales.Locale(source_guild_payload["preferred_locale"]),
             afk_timeout=datetime.timedelta(seconds=source_guild_payload["afk_timeout"]),
             roles=roles,
             channels=channels,
@@ -2920,7 +2921,7 @@ def deserialize_my_user(self, payload: data_binding.JSONObject) -> user_models.O
             is_bot=user_fields.is_bot,
             is_system=user_fields.is_system,
             is_mfa_enabled=payload["mfa_enabled"],
-            locale=payload.get("locale"),
+            locale=locales.Locale(payload["locale"]),
             is_verified=payload.get("verified"),
             email=payload.get("email"),
             flags=user_models.UserFlag(payload["flags"]),
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index 5952faab..1054094b 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -58,6 +58,7 @@
 from hikari import files
 from hikari import guilds
 from hikari import iterators
+from hikari import locales
 from hikari import permissions as permissions_
 from hikari import scheduled_events
 from hikari import snowflakes
@@ -2325,7 +2326,7 @@ async def edit_guild(
         public_updates_channel: undefined.UndefinedNoneOr[
             snowflakes.SnowflakeishOr[channels_.GuildTextChannel]
         ] = undefined.UNDEFINED,
-        preferred_locale: undefined.UndefinedOr[str] = undefined.UNDEFINED,
+        preferred_locale: undefined.UndefinedOr[typing.Union[str, locales.Locale]] = undefined.UNDEFINED,
         reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
     ) -> guilds.RESTGuild:
         route = routes.PATCH_GUILD.compile(guild=guild)
diff --git a/hikari/interactions/command_interactions.py b/hikari/interactions/command_interactions.py
index e39f0b0c..01ae6094 100644
--- a/hikari/interactions/command_interactions.py
+++ b/hikari/interactions/command_interactions.py
@@ -49,6 +49,7 @@
 
 if typing.TYPE_CHECKING:
     from hikari import guilds
+    from hikari import locales
     from hikari import messages as messages_
     from hikari import permissions as permissions_
     from hikari import users as users_
@@ -184,7 +185,7 @@ class BaseCommandInteraction(base_interactions.PartialInteraction):
     This will be `builtins.None` for command interactions triggered in DMs.
     """
 
-    guild_locale: typing.Optional[str] = attr.field(eq=False, hash=False, repr=True)
+    guild_locale: typing.Optional[typing.Union[str, locales.Locale]] = attr.field(eq=False, hash=False, repr=True)
     """The preferred language of the guild this command interaction was triggered in.
 
     This will be `builtins.None` for command interactions triggered in DMs.
@@ -207,7 +208,7 @@ class BaseCommandInteraction(base_interactions.PartialInteraction):
     user: users_.User = attr.field(eq=False, hash=False, repr=True)
     """The user who triggered this command interaction."""
 
-    locale: str = attr.field(eq=False, hash=False, repr=True)
+    locale: typing.Union[str, locales.Locale] = attr.field(eq=False, hash=False, repr=True)
     """The selected language of the user who triggered this command interaction."""
 
     command_id: snowflakes.Snowflake = attr.field(eq=False, hash=False, repr=True)
diff --git a/hikari/interactions/component_interactions.py b/hikari/interactions/component_interactions.py
index a0e2ffa2..ff82df61 100644
--- a/hikari/interactions/component_interactions.py
+++ b/hikari/interactions/component_interactions.py
@@ -35,6 +35,7 @@
 
 if typing.TYPE_CHECKING:
     from hikari import guilds
+    from hikari import locales
     from hikari import messages
     from hikari import snowflakes
     from hikari import users
@@ -107,7 +108,7 @@ class ComponentInteraction(base_interactions.MessageResponseMixin[ComponentRespo
     This will be `builtins.None` for component interactions triggered in DMs.
     """
 
-    guild_locale: typing.Optional[str] = attr.field(eq=False, hash=False, repr=True)
+    guild_locale: typing.Optional[typing.Union[str, locales.Locale]] = attr.field(eq=False, hash=False, repr=True)
     """The preferred language of the guild this component interaction was triggered in.
 
     This will be `builtins.None` for component interactions triggered in DMs.
@@ -133,7 +134,7 @@ class ComponentInteraction(base_interactions.MessageResponseMixin[ComponentRespo
     user: users.User = attr.field(eq=False, hash=False, repr=True)
     """The user who triggered this interaction."""
 
-    locale: str = attr.field(eq=False, hash=False, repr=True)
+    locale: typing.Union[str, locales.Locale] = attr.field(eq=False, hash=False, repr=True)
     """The selected language of the user who triggered this component interaction."""
 
     def build_response(self, type_: _ImmediateTypesT, /) -> special_endpoints.InteractionMessageBuilder:
diff --git a/hikari/locales.py b/hikari/locales.py
new file mode 100644
index 00000000..92980883
--- /dev/null
+++ b/hikari/locales.py
@@ -0,0 +1,125 @@
+# -*- coding: utf-8 -*-
+# cython: language_level=3
+# Copyright (c) 2020 Nekokatt
+# Copyright (c) 2021-present davfsa
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+"""Enum of Discord accepted locales."""
+from __future__ import annotations
+
+__all__ = ["Locale"]
+
+import typing
+
+from hikari.internal import enums
+
+
+@typing.final
+class Locale(str, enums.Enum):
+    """Possible user/guild locales."""
+
+    DA = "da"
+    """Danish"""
+
+    DE = "de"
+    """German"""
+
+    EN_GB = "en-GB"
+    """English, UK"""
+
+    EN_US = "en-US"
+    """English, US"""
+
+    ES_ES = "es-ES"
+    """Spanish"""
+
+    FR = "fr"
+    """French"""
+
+    HR = "hr"
+    """Croatian"""
+
+    IT = "it"
+    """Italian"""
+
+    LT = "lt"
+    """Lithuanian"""
+
+    HU = "hu"
+    """Hungarian"""
+
+    NL = "nl"
+    """Dutch"""
+
+    NO = "no"
+    """Norwegian"""
+
+    OL = "pl"
+    """Polish"""
+
+    PT_BR = "pt-BR"
+    """Portuguese, Bralizian"""
+
+    RO = "ro"
+    """Romian"""
+
+    FI = "fi"
+    """Finnish"""
+
+    SV_SE = "sv-SE"
+    """Swedish"""
+
+    VI = "vi"
+    """Vietnamese"""
+
+    TR = "tr"
+    """Turkish"""
+
+    CS = "cs"
+    """Czech"""
+
+    EL = "el"
+    """Greek"""
+
+    BG = "bg"
+    """Bulgarian"""
+
+    RU = "ru"
+    """Russian"""
+
+    UK = "uk"
+    """Ukrainian"""
+
+    HI = "hi"
+    """Hindi"""
+
+    TH = "th"
+    """Thai"""
+
+    ZH_CN = "zh-CN"
+    """Chinese, China"""
+
+    JA = "ja"
+    """Japanese"""
+
+    ZH_TW = "zh-TW"
+    """Chinese, Taiwan"""
+
+    KO = "ko"
+    """Korean"""
diff --git a/hikari/templates.py b/hikari/templates.py
index dd1cbdca..efbce520 100644
--- a/hikari/templates.py
+++ b/hikari/templates.py
@@ -38,6 +38,7 @@
 
     from hikari import channels as channels_
     from hikari import colors
+    from hikari import locales
     from hikari import permissions as permissions_
     from hikari import snowflakes
     from hikari import users
@@ -91,7 +92,7 @@ class TemplateGuild(guilds.PartialGuild):
     )
     """The setting for the explicit content filter in this guild."""
 
-    preferred_locale: str = attr.field(eq=False, hash=False, repr=False)
+    preferred_locale: typing.Union[str, locales.Locale] = attr.field(eq=False, hash=False, repr=False)
     """The preferred locale to use for this guild.
 
     This can only be change if `GuildFeature.COMMUNITY` is in `Guild.features`
diff --git a/hikari/users.py b/hikari/users.py
index cbf3786f..f8d9d2ca 100644
--- a/hikari/users.py
+++ b/hikari/users.py
@@ -45,6 +45,7 @@
     from hikari import embeds as embeds_
     from hikari import files
     from hikari import guilds
+    from hikari import locales
     from hikari import messages
     from hikari.api import special_endpoints
 
@@ -756,7 +757,7 @@ class OwnUser(UserImpl):
     is_mfa_enabled: bool = attr.field(eq=False, hash=False, repr=False)
     """Whether the user's account has multi-factor authentication enabled."""
 
-    locale: typing.Optional[str] = attr.field(eq=False, hash=False, repr=False)
+    locale: typing.Optional[typing.Union[str, locales.Locale]] = attr.field(eq=False, hash=False, repr=False)
     """The user's set language. This is not provided by the `READY` event."""
 
     is_verified: typing.Optional[bool] = attr.field(eq=False, hash=False, repr=False)


Test patch:
diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py
index 404df0fe..0d2e1fa9 100644
--- a/tests/hikari/impl/test_entity_factory.py
+++ b/tests/hikari/impl/test_entity_factory.py
@@ -36,6 +36,7 @@
 from hikari import files
 from hikari import guilds as guild_models
 from hikari import invites as invite_models
+from hikari import locales
 from hikari import messages as message_models
 from hikari import permissions as permission_models
 from hikari import presences as presence_models
@@ -2340,6 +2341,7 @@ def test_deserialize_rest_guild(
         assert guild.premium_tier == guild_models.GuildPremiumTier.TIER_2
         assert guild.premium_subscription_count == 1
         assert guild.preferred_locale == "en-GB"
+        assert guild.preferred_locale is locales.Locale.EN_GB
         assert guild.public_updates_channel_id == 33333333
         assert guild.approximate_member_count == 15
         assert guild.approximate_active_member_count == 7
@@ -2550,6 +2552,7 @@ def test_deserialize_gateway_guild(
         assert guild.premium_tier == guild_models.GuildPremiumTier.TIER_2
         assert guild.premium_subscription_count == 1
         assert guild.preferred_locale == "en-GB"
+        assert guild.preferred_locale is locales.Locale.EN_GB
         assert guild.public_updates_channel_id == 33333333
         assert guild.nsfw_level == guild_models.GuildNSFWLevel.DEFAULT
 
@@ -3085,7 +3088,9 @@ def test_deserialize_command_interaction(
         assert interaction.channel_id == 49949494
         assert interaction.guild_id == 43123123
         assert interaction.locale == "es-ES"
+        assert interaction.locale is locales.Locale.ES_ES
         assert interaction.guild_locale == "en-US"
+        assert interaction.guild_locale is locales.Locale.EN_US
         assert interaction.member == entity_factory_impl._deserialize_interaction_member(
             interaction_member_payload, guild_id=43123123
         )
@@ -3193,7 +3198,9 @@ def test_deserialize_autocomplete_interaction(
         assert interaction.channel_id == 49949494
         assert interaction.guild_id == 43123123
         assert interaction.locale == "es-ES"
+        assert interaction.locale is locales.Locale.ES_ES
         assert interaction.guild_locale == "en-US"
+        assert interaction.guild_locale is locales.Locale.EN_US
         assert interaction.resolved == entity_factory_impl._deserialize_resolved_option_data(
             interaction_resolved_data_payload, guild_id=43123123
         )
@@ -3488,7 +3495,9 @@ def test_deserialize_component_interaction(
         assert interaction.user is interaction.member.user
         assert interaction.values == ["1", "2", "67"]
         assert interaction.locale == "es-ES"
+        assert interaction.locale is locales.Locale.ES_ES
         assert interaction.guild_locale == "en-US"
+        assert interaction.guild_locale is locales.Locale.EN_US
         assert isinstance(interaction, component_interactions.ComponentInteraction)
 
     def test_deserialize_component_interaction_with_undefined_fields(
@@ -5195,6 +5204,7 @@ def test_deserialize_template(
         )
         assert template.source_guild.explicit_content_filter is guild_models.GuildExplicitContentFilterLevel.ALL_MEMBERS
         assert template.source_guild.preferred_locale == "en-GB"
+        assert template.source_guild.preferred_locale is locales.Locale.EN_GB
         assert template.source_guild.afk_timeout == datetime.timedelta(seconds=3600)
 
         # TemplateRole
@@ -5341,6 +5351,7 @@ def test_deserialize_my_user(self, entity_factory_impl, mock_app, my_user_payloa
         assert my_user.is_system is True
         assert my_user.is_mfa_enabled is True
         assert my_user.locale == "en-US"
+        assert my_user.locale is locales.Locale.EN_US
         assert my_user.is_verified is True
         assert my_user.email == "blahblah@blah.blah"
         assert my_user.flags == user_models.UserFlag.PARTNERED_SERVER_OWNER | user_models.UserFlag.DISCORD_EMPLOYEE