i18n.py 3.9 KB

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