translate.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. # GNU MediaGoblin -- federated, autonomous media hosting
  2. # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. import gettext
  17. import pkg_resources
  18. import six
  19. from babel import localedata
  20. from babel.support import LazyProxy
  21. from mediagoblin import mg_globals
  22. ###################
  23. # Translation tools
  24. ###################
  25. AVAILABLE_LOCALES = None
  26. TRANSLATIONS_PATH = pkg_resources.resource_filename(
  27. 'mediagoblin', 'i18n')
  28. # Known RTL languages
  29. KNOWN_RTL = set(["ar", "fa", "he", "iw", "ur", "yi", "ji"])
  30. def is_rtl(lang):
  31. """Returns true when the local language is right to left"""
  32. return lang in KNOWN_RTL
  33. def set_available_locales():
  34. """Set available locales for which we have translations"""
  35. global AVAILABLE_LOCALES
  36. locales=['en', 'en_US'] # these are available without translations
  37. for locale in localedata.locale_identifiers():
  38. if gettext.find('mediagoblin', TRANSLATIONS_PATH, [locale]):
  39. locales.append(locale)
  40. AVAILABLE_LOCALES = locales
  41. class ReallyLazyProxy(LazyProxy):
  42. """
  43. Like LazyProxy, except that it doesn't cache the value ;)
  44. """
  45. def __init__(self, func, *args, **kwargs):
  46. super(ReallyLazyProxy, self).__init__(func, *args, **kwargs)
  47. object.__setattr__(self, '_is_cache_enabled', False)
  48. def __repr__(self):
  49. return "<%s for %s(%r, %r)>" % (
  50. self.__class__.__name__,
  51. self._func,
  52. self._args,
  53. self._kwargs)
  54. def locale_to_lower_upper(locale):
  55. """
  56. Take a locale, regardless of style, and format it like "en_US"
  57. """
  58. if '-' in locale:
  59. lang, country = locale.split('-', 1)
  60. return '%s_%s' % (lang.lower(), country.upper())
  61. elif '_' in locale:
  62. lang, country = locale.split('_', 1)
  63. return '%s_%s' % (lang.lower(), country.upper())
  64. else:
  65. return locale.lower()
  66. def locale_to_lower_lower(locale):
  67. """
  68. Take a locale, regardless of style, and format it like "en_us"
  69. """
  70. if '_' in locale:
  71. lang, country = locale.split('_', 1)
  72. return '%s-%s' % (lang.lower(), country.lower())
  73. else:
  74. return locale.lower()
  75. def get_locale_from_request(request):
  76. """
  77. Return most appropriate language based on prefs/request request
  78. """
  79. request_args = (request.args, request.form)[request.method=='POST']
  80. if 'lang' in request_args:
  81. # User explicitely demanded a language, normalize lower_uppercase
  82. target_lang = locale_to_lower_upper(request_args['lang'])
  83. elif 'target_lang' in request.session:
  84. # TODO: Uh, ohh, this is never ever set anywhere?
  85. target_lang = request.session['target_lang']
  86. else:
  87. # Pull the most acceptable language based on browser preferences
  88. # This returns one of AVAILABLE_LOCALES which is aready case-normalized.
  89. # Note: in our tests request.accept_languages is None, so we need
  90. # to explicitely fallback to en here.
  91. target_lang = request.accept_languages.best_match(AVAILABLE_LOCALES) \
  92. or "en_US"
  93. return target_lang
  94. SETUP_GETTEXTS = {}
  95. def get_gettext_translation(locale):
  96. """
  97. Return the gettext instance based on this locale
  98. """
  99. # Later on when we have plugins we may want to enable the
  100. # multi-translations system they have so we can handle plugin
  101. # translations too
  102. # TODO: fallback nicely on translations from pt_PT to pt if not
  103. # available, etc.
  104. if locale in SETUP_GETTEXTS:
  105. this_gettext = SETUP_GETTEXTS[locale]
  106. else:
  107. this_gettext = gettext.translation(
  108. 'mediagoblin', TRANSLATIONS_PATH, [locale], fallback=True)
  109. if localedata.exists(locale):
  110. SETUP_GETTEXTS[locale] = this_gettext
  111. return this_gettext
  112. def set_thread_locale(locale):
  113. """Set the current translation for this thread"""
  114. mg_globals.thread_scope.translations = get_gettext_translation(locale)
  115. def pass_to_ugettext(*args, **kwargs):
  116. """
  117. Pass a translation on to the appropriate ugettext method.
  118. The reason we can't have a global ugettext method is because
  119. mg_globals gets swapped out by the application per-request.
  120. """
  121. if six.PY2:
  122. return mg_globals.thread_scope.translations.ugettext(*args, **kwargs)
  123. return mg_globals.thread_scope.translations.gettext(*args, **kwargs)
  124. def pass_to_ungettext(*args, **kwargs):
  125. """
  126. Pass a translation on to the appropriate ungettext method.
  127. The reason we can't have a global ugettext method is because
  128. mg_globals gets swapped out by the application per-request.
  129. """
  130. if six.PY2:
  131. return mg_globals.thread_scope.translations.ungettext(*args, **kwargs)
  132. return mg_globals.thread_scope.translations.ngettext(*args, **kwargs)
  133. def lazy_pass_to_ugettext(*args, **kwargs):
  134. """
  135. Lazily pass to ugettext.
  136. This is useful if you have to define a translation on a module
  137. level but you need it to not translate until the time that it's
  138. used as a string. For example, in:
  139. def func(self, message=_('Hello boys and girls'))
  140. you would want to use the lazy version for _.
  141. """
  142. return ReallyLazyProxy(pass_to_ugettext, *args, **kwargs)
  143. def pass_to_ngettext(*args, **kwargs):
  144. """
  145. Pass a translation on to the appropriate ngettext method.
  146. The reason we can't have a global ngettext method is because
  147. mg_globals gets swapped out by the application per-request.
  148. """
  149. return mg_globals.thread_scope.translations.ngettext(
  150. *args, **kwargs)
  151. def lazy_pass_to_ngettext(*args, **kwargs):
  152. """
  153. Lazily pass to ngettext.
  154. This is useful if you have to define a translation on a module
  155. level but you need it to not translate until the time that it's
  156. used as a string.
  157. """
  158. return ReallyLazyProxy(pass_to_ngettext, *args, **kwargs)
  159. def lazy_pass_to_ungettext(*args, **kwargs):
  160. """
  161. Lazily pass to ungettext.
  162. This is useful if you have to define a translation on a module
  163. level but you need it to not translate until the time that it's
  164. used as a string.
  165. """
  166. return ReallyLazyProxy(pass_to_ungettext, *args, **kwargs)
  167. def fake_ugettext_passthrough(string):
  168. """
  169. Fake a ugettext call for extraction's sake ;)
  170. In wtforms there's a separate way to define a method to translate
  171. things... so we just need to mark up the text so that it can be
  172. extracted, not so that it's actually run through gettext.
  173. """
  174. return string