Languages.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. <?php
  2. namespace Shaarli;
  3. use Gettext\GettextTranslator;
  4. use Gettext\Merge;
  5. use Gettext\Translations;
  6. use Gettext\Translator;
  7. use Gettext\TranslatorInterface;
  8. use Shaarli\Config\ConfigManager;
  9. /**
  10. * Class Languages
  11. *
  12. * Load Shaarli translations using 'gettext/gettext'.
  13. * This class allows to either use PHP gettext extension, or a PHP implementation of gettext,
  14. * with a fixed language, or dynamically using autoLocale().
  15. *
  16. * Translation files PO/MO files follow gettext standard and must be placed under:
  17. * <translation path>/<language>/LC_MESSAGES/<domain>.[po|mo]
  18. *
  19. * Pros/cons:
  20. * - gettext extension is faster
  21. * - gettext is very system dependent (PHP extension, the locale must be installed, and web server reloaded)
  22. *
  23. * Settings:
  24. * - translation.mode:
  25. * - auto: use default setting (PHP implementation)
  26. * - php: use PHP implementation
  27. * - gettext: use gettext wrapper
  28. * - translation.language:
  29. * - auto: use autoLocale() and the language change according to user HTTP headers
  30. * - fixed language: e.g. 'fr'
  31. * - translation.extensions:
  32. * - domain => translation_path: allow plugins and themes to extend the defaut extension
  33. * The domain must be unique, and translation path must be relative, and contains the tree mentioned above.
  34. *
  35. * @package Shaarli
  36. */
  37. class Languages
  38. {
  39. /**
  40. * Core translations domain
  41. */
  42. const DEFAULT_DOMAIN = 'shaarli';
  43. /**
  44. * @var TranslatorInterface
  45. */
  46. protected $translator;
  47. /**
  48. * @var string
  49. */
  50. protected $language;
  51. /**
  52. * @var ConfigManager
  53. */
  54. protected $conf;
  55. /**
  56. * Languages constructor.
  57. *
  58. * @param string $language lang determined by autoLocale(), can be override.
  59. * @param ConfigManager $conf instance.
  60. */
  61. public function __construct($language, $conf)
  62. {
  63. $this->conf = $conf;
  64. $confLanguage = $this->conf->get('translation.language', 'auto');
  65. if ($confLanguage === 'auto' || ! $this->isValidLanguage($confLanguage)) {
  66. $this->language = substr($language, 0, 5);
  67. } else {
  68. $this->language = $confLanguage;
  69. }
  70. if (! extension_loaded('gettext')
  71. || in_array($this->conf->get('translation.mode', 'auto'), ['auto', 'php'])
  72. ) {
  73. $this->initPhpTranslator();
  74. } else {
  75. $this->initGettextTranslator();
  76. }
  77. // Register default functions (e.g. '__()') to use our Translator
  78. $this->translator->register();
  79. }
  80. /**
  81. * Initialize the translator using php gettext extension (gettext dependency act as a wrapper).
  82. */
  83. protected function initGettextTranslator ()
  84. {
  85. $this->translator = new GettextTranslator();
  86. $this->translator->setLanguage($this->language);
  87. $this->translator->loadDomain(self::DEFAULT_DOMAIN, 'inc/languages');
  88. foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) {
  89. if ($domain !== self::DEFAULT_DOMAIN) {
  90. $this->translator->loadDomain($domain, $translationPath, false);
  91. }
  92. }
  93. }
  94. /**
  95. * Initialize the translator using a PHP implementation of gettext.
  96. *
  97. * Note that if language po file doesn't exist, errors are ignored (e.g. not installed language).
  98. */
  99. protected function initPhpTranslator()
  100. {
  101. $this->translator = new Translator();
  102. $translations = new Translations();
  103. // Core translations
  104. try {
  105. /** @var Translations $translations */
  106. $translations = $translations->addFromPoFile('inc/languages/'. $this->language .'/LC_MESSAGES/shaarli.po');
  107. $translations->setDomain('shaarli');
  108. $this->translator->loadTranslations($translations);
  109. } catch (\InvalidArgumentException $e) {}
  110. // Extension translations (plugins, themes, etc.).
  111. foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) {
  112. if ($domain === self::DEFAULT_DOMAIN) {
  113. continue;
  114. }
  115. try {
  116. /** @var Translations $extension */
  117. $extension = Translations::fromPoFile($translationPath . $this->language .'/LC_MESSAGES/'. $domain .'.po');
  118. $extension->setDomain($domain);
  119. $this->translator->loadTranslations($extension);
  120. } catch (\InvalidArgumentException $e) {}
  121. }
  122. }
  123. /**
  124. * Checks if a language string is valid.
  125. *
  126. * @param string $language e.g. 'fr' or 'en_US'
  127. *
  128. * @return bool true if valid, false otherwise
  129. */
  130. protected function isValidLanguage($language)
  131. {
  132. return preg_match('/^[a-z]{2}(_[A-Z]{2})?/', $language) === 1;
  133. }
  134. }