i18n.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import sqlalchemy as sa
  2. from sqlalchemy.ext.compiler import compiles
  3. from sqlalchemy.ext.hybrid import hybrid_property
  4. from sqlalchemy.sql.expression import ColumnElement
  5. from .exceptions import ImproperlyConfigured
  6. try:
  7. import babel
  8. import babel.dates
  9. except ImportError:
  10. babel = None
  11. def get_locale():
  12. try:
  13. return babel.Locale('en')
  14. except AttributeError:
  15. # As babel is optional, we may raise an AttributeError accessing it
  16. raise ImproperlyConfigured(
  17. 'Could not load get_locale function using Babel. Either '
  18. 'install Babel or make a similar function and override it '
  19. 'in this module.'
  20. )
  21. def cast_locale(obj, locale, attr):
  22. """
  23. Cast given locale to string. Supports also callbacks that return locales.
  24. :param obj:
  25. Object or class to use as a possible parameter to locale callable
  26. :param locale:
  27. Locale object or string or callable that returns a locale.
  28. """
  29. if callable(locale):
  30. try:
  31. locale = locale(obj, attr.key)
  32. except TypeError:
  33. try:
  34. locale = locale(obj)
  35. except TypeError:
  36. locale = locale()
  37. if isinstance(locale, babel.Locale):
  38. return str(locale)
  39. return locale
  40. class cast_locale_expr(ColumnElement):
  41. inherit_cache = False
  42. def __init__(self, cls, locale, attr):
  43. self.cls = cls
  44. self.locale = locale
  45. self.attr = attr
  46. @compiles(cast_locale_expr)
  47. def compile_cast_locale_expr(element, compiler, **kw):
  48. locale = cast_locale(element.cls, element.locale, element.attr)
  49. if isinstance(locale, str):
  50. return f"'{locale}'"
  51. return compiler.process(locale)
  52. class TranslationHybrid:
  53. def __init__(self, current_locale, default_locale, default_value=None):
  54. if babel is None:
  55. raise ImproperlyConfigured(
  56. 'You need to install babel in order to use TranslationHybrid.'
  57. )
  58. self.current_locale = current_locale
  59. self.default_locale = default_locale
  60. self.default_value = default_value
  61. def getter_factory(self, attr):
  62. """
  63. Return a hybrid_property getter function for given attribute. The
  64. returned getter first checks if object has translation for current
  65. locale. If not it tries to get translation for default locale. If there
  66. is no translation found for default locale it returns None.
  67. """
  68. def getter(obj):
  69. current_locale = cast_locale(obj, self.current_locale, attr)
  70. try:
  71. return getattr(obj, attr.key)[current_locale]
  72. except (TypeError, KeyError):
  73. default_locale = cast_locale(obj, self.default_locale, attr)
  74. try:
  75. return getattr(obj, attr.key)[default_locale]
  76. except (TypeError, KeyError):
  77. return self.default_value
  78. return getter
  79. def setter_factory(self, attr):
  80. def setter(obj, value):
  81. if getattr(obj, attr.key) is None:
  82. setattr(obj, attr.key, {})
  83. locale = cast_locale(obj, self.current_locale, attr)
  84. getattr(obj, attr.key)[locale] = value
  85. return setter
  86. def expr_factory(self, attr):
  87. def expr(cls):
  88. cls_attr = getattr(cls, attr.key)
  89. current_locale = cast_locale_expr(cls, self.current_locale, attr)
  90. default_locale = cast_locale_expr(cls, self.default_locale, attr)
  91. return sa.func.coalesce(
  92. cls_attr[current_locale],
  93. cls_attr[default_locale]
  94. )
  95. return expr
  96. def __call__(self, attr):
  97. return hybrid_property(
  98. fget=self.getter_factory(attr),
  99. fset=self.setter_factory(attr),
  100. expr=self.expr_factory(attr)
  101. )