Languages.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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 overridden.
  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. // Auto mode or invalid parameter, use the detected language.
  66. // If the detected language is invalid, it doesn't matter, it will use English.
  67. if ($confLanguage === 'auto' || ! $this->isValidLanguage($confLanguage)) {
  68. $this->language = substr($language, 0, 5);
  69. } else {
  70. $this->language = $confLanguage;
  71. }
  72. if (! extension_loaded('gettext')
  73. || in_array($this->conf->get('translation.mode', 'auto'), ['auto', 'php'])
  74. ) {
  75. $this->initPhpTranslator();
  76. } else {
  77. $this->initGettextTranslator();
  78. }
  79. // Register default functions (e.g. '__()') to use our Translator
  80. $this->translator->register();
  81. }
  82. /**
  83. * Initialize the translator using php gettext extension (gettext dependency act as a wrapper).
  84. */
  85. protected function initGettextTranslator ()
  86. {
  87. $this->translator = new GettextTranslator();
  88. $this->translator->setLanguage($this->language);
  89. $this->translator->loadDomain(self::DEFAULT_DOMAIN, 'inc/languages');
  90. foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) {
  91. if ($domain !== self::DEFAULT_DOMAIN) {
  92. $this->translator->loadDomain($domain, $translationPath, false);
  93. }
  94. }
  95. }
  96. /**
  97. * Initialize the translator using a PHP implementation of gettext.
  98. *
  99. * Note that if language po file doesn't exist, errors are ignored (e.g. not installed language).
  100. */
  101. protected function initPhpTranslator()
  102. {
  103. $this->translator = new Translator();
  104. $translations = new Translations();
  105. // Core translations
  106. try {
  107. /** @var Translations $translations */
  108. $translations = $translations->addFromPoFile('inc/languages/'. $this->language .'/LC_MESSAGES/shaarli.po');
  109. $translations->setDomain('shaarli');
  110. $this->translator->loadTranslations($translations);
  111. } catch (\InvalidArgumentException $e) {}
  112. // Extension translations (plugins, themes, etc.).
  113. foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) {
  114. if ($domain === self::DEFAULT_DOMAIN) {
  115. continue;
  116. }
  117. try {
  118. /** @var Translations $extension */
  119. $extension = Translations::fromPoFile($translationPath . $this->language .'/LC_MESSAGES/'. $domain .'.po');
  120. $extension->setDomain($domain);
  121. $this->translator->loadTranslations($extension);
  122. } catch (\InvalidArgumentException $e) {}
  123. }
  124. }
  125. /**
  126. * Checks if a language string is valid.
  127. *
  128. * @param string $language e.g. 'fr' or 'en_US'
  129. *
  130. * @return bool true if valid, false otherwise
  131. */
  132. protected function isValidLanguage($language)
  133. {
  134. return preg_match('/^[a-z]{2}(_[A-Z]{2})?/', $language) === 1;
  135. }
  136. /**
  137. * Get the list of available languages for Shaarli.
  138. *
  139. * @return array List of available languages, with their label.
  140. */
  141. public static function getAvailableLanguages()
  142. {
  143. return [
  144. 'auto' => t('Automatic'),
  145. 'en' => t('English'),
  146. 'fr' => t('French'),
  147. ];
  148. }
  149. }