File size: 4,610 Bytes
9c6594c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
from __future__ import annotations
import re
import warnings
from typing import Any
class I18nData:
"""
A class that wraps a translation key with metadata.
This object will be serialized and sent to the frontend, where the actual
translation will happen using the frontend's i18n system.
"""
def __init__(self, key: str):
"""
Initialize a I18nData object.
Args:
key: The translation key to be translated in the frontend.
"""
self.key = key
self._type = "translation_metadata"
def to_dict(self) -> dict[str, Any]:
"""
Convert the I18nData object to a dictionary for serialization.
This allows the frontend to recognize it as a translatable object.
"""
return {"__type__": self._type, "key": self.key}
def __str__(self) -> str:
"""
String representation of the I18nData object.
Used when the object is converted to a string.
This returns a special format that can be recognized by the frontend
as needing translation.
"""
import json
return f"__i18n__{json.dumps(self.to_dict())}"
def __repr__(self) -> str:
"""
Representation of the I18nData object for debugging.
"""
return self.__str__()
def __add__(self, other):
"""
Handle string concatenation (self + other).
"""
return str(self) + str(other)
def __radd__(self, other):
"""
Handle string concatenation (other + self).
"""
return str(other) + str(self)
def __getattr__(self, name):
"""
Handle attribute access for I18nData.
This makes it possible to use I18nData objects in contexts
that expect strings with methods.
"""
if name.startswith("__") and name.endswith("__"):
raise AttributeError(f"{self.__class__.__name__} has no attribute {name}")
def method(*_args, **_kwargs):
return self
return method
def tojson(self) -> dict[str, Any]:
"""
Convert the I18nData object to a JSON-serializable dictionary.
This is used by the default Python JSON serializer.
"""
return self.to_dict()
class I18n:
"""
Handles internationalization (i18n) for Gradio applications.
Stores translation dictionaries and provides a method to retrieve translation keys.
The translation lookup happens on the frontend based on the browser's locale
and the provided translation dictionaries.
"""
# BCP 47 language tag regex pattern
_LOCALE_PATTERN = re.compile(r"^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$")
def __init__(self, **translations: dict[str, str]):
"""
Initializes the I18n class.
Args:
**translations: Each keyword argument should be a locale code (e.g., "en", "fr") with a
dictionary value, which maps translation keys to translated strings.
Example: gr.I18n(en={"greeting": "Hello"}, es={"greeting": "Hola"})
These translations can be passed to the frontend for use there.
"""
self.translations = {}
for locale, translation_dict in translations.items():
if not self._is_valid_locale(locale):
warnings.warn(
f"Invalid locale code: '{locale}'. Locale codes should follow BCP 47 format (e.g., 'en', 'en-US'). "
f"This locale will still be included, but may not work correctly.",
UserWarning,
)
self.translations[locale] = translation_dict
def _is_valid_locale(self, locale: str) -> bool:
return bool(self._LOCALE_PATTERN.match(locale))
def __call__(self, key: str) -> I18nData:
"""
Returns a I18nData object containing the translation key.
This metadata object will be serialized and sent to the frontend,
where it will be translated by the frontend's i18n system.
Args:
key: The key to identify the translation string (e.g., "submit_button").
Returns:
A I18nData object containing the translation key.
"""
return I18nData(key)
@property
def translations_dict(self) -> dict[str, dict[str, str]]:
"""
Returns the dictionary of translations provided during initialization.
These can be passed to the frontend for use in its translation system.
"""
return self.translations
|