__init__.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. # MODULE: grass
  2. #
  3. # AUTHOR(S): Vaclav Petras <wenzeslaus gmail com>
  4. #
  5. # PURPOSE: Top level file of the grass package and its initialization
  6. #
  7. # COPYRIGHT: (C) 2021 Vaclav Petras, and by the GRASS Development Team
  8. #
  9. # This program is free software under the GNU General Public
  10. # License (>=v2). Read the file COPYING that comes with GRASS
  11. # for details.
  12. """Top-level GRASS GIS Python package
  13. Importing the package (or any subpackage) initializes translation functions
  14. so that the function ``_`` appears in the global namespace (as an additional build-in).
  15. """
  16. import builtins as _builtins
  17. import os
  18. # Setup translations
  19. #
  20. # The translations in the GRASS GIS Python package grass, GRASS Python modules
  21. # (scripts), and the wxPython GUI (wxGUI) are handled as application translations,
  22. # rather than Python module or package translations. This means that that translation
  23. # function called `_` (underscore) is added to buildins namespace. When the grass
  24. # package or any subpackage (or an object from there) is imported, the `_` function
  25. # becomes available globally (i.e. for all Python code in the same process). This is
  26. # the good part.
  27. # Unfortunately, creating `_` in the global namespace has unitended consequences such
  28. # as issues in interactive shells, with doctests, and it is conflicting with a common
  29. # practice of using `_` for unused variables. This is the same behavior as with the
  30. # common function `gettext.install()` which also adds `_` into the global namespace.
  31. # The solution to this is to use the module translations approach instead of
  32. # application translation approach. Without changing the overall approach, the current
  33. # code can be modified to allow for using imports of translation functions instead of
  34. # relying on the buildins when desired as a transitional state before removing the
  35. # modification of buildins.
  36. #
  37. # The current code mitigates two other issues associated with `gettext.install()`
  38. # approach: First, it delays initialization of translations to the time when they are
  39. # needed instead of doing it during import time (and possibly failing when
  40. # environmental variables pointing to the source file are not set properly). Second,
  41. # it adds multiple domains as fallback unlike `gettext.install()` which simply uses
  42. # the last used domain.
  43. #
  44. # For more info, please check the following links:
  45. # - https://docs.python.org/3/library/gettext.html#gettext.translation
  46. # - https://github.com/python/cpython/blob/main/Lib/gettext.py (esp. install function)
  47. # - https://pymotw.com/3//gettext/index.html#application-vs-module-localization
  48. # - https://www.wefearchange.org/2012/06/the-right-way-to-internationalize-your.html
  49. def _translate(text):
  50. """Get translated version of text
  51. The first call to this function initializes translations, i.e., simply importing
  52. the package does not require the translations to be availabe. However, a first
  53. call to translate a message will do the initialization first before translating
  54. the message.
  55. """
  56. if _translate.translation is None:
  57. # Initialize translations if needed. This should happen (only) during the
  58. # the first call of the function.
  59. try:
  60. import gettext # pylint: disable=import-outside-toplevel
  61. gisbase = os.environ["GISBASE"]
  62. locale_dir = os.path.join(gisbase, "locale")
  63. # With fallback set to True, not finding the translations files for
  64. # a language or domain results in a use of null translation, so this
  65. # does not raise an exception even if the locale settings is broken
  66. # or the translation files were not installed.
  67. fallback = True
  68. translation = gettext.translation(
  69. "grasslibs", locale_dir, fallback=fallback
  70. )
  71. # Add other domains as fallback.
  72. translation.add_fallback(
  73. gettext.translation("grassmods", locale_dir, fallback=fallback)
  74. )
  75. translation.add_fallback(
  76. gettext.translation("grasswxpy", locale_dir, fallback=fallback)
  77. )
  78. # Store the resulting translation object.
  79. _translate.translation = translation
  80. except (KeyError, ImportError):
  81. # If the environmental variable is not set or there is no gettext,
  82. # use null translation as an ultimate fallback.
  83. _translate.translation = gettext.NullTranslations()
  84. return _translate.translation.gettext(text)
  85. # Initialize the translation attribute of the translate function to indicate
  86. # that the translations are not initialized.
  87. _translate.translation = None
  88. _builtins.__dict__["_"] = _translate
  89. __all__ = ["script", "temporal"]
  90. if os.path.exists(os.path.join(os.path.dirname(__file__), "lib", "__init__.py")):
  91. __all__.append("lib")