]>
Commit | Line | Data |
---|---|---|
1 | <?php | |
2 | ||
3 | namespace Shaarli; | |
4 | ||
5 | use Gettext\GettextTranslator; | |
6 | use Gettext\Translations; | |
7 | use Gettext\Translator; | |
8 | use Gettext\TranslatorInterface; | |
9 | use Shaarli\Config\ConfigManager; | |
10 | ||
11 | /** | |
12 | * Class Languages | |
13 | * | |
14 | * Load Shaarli translations using 'gettext/gettext'. | |
15 | * This class allows to either use PHP gettext extension, or a PHP implementation of gettext, | |
16 | * with a fixed language, or dynamically using autoLocale(). | |
17 | * | |
18 | * Translation files PO/MO files follow gettext standard and must be placed under: | |
19 | * <translation path>/<language>/LC_MESSAGES/<domain>.[po|mo] | |
20 | * | |
21 | * Pros/cons: | |
22 | * - gettext extension is faster | |
23 | * - gettext is very system dependent (PHP extension, the locale must be installed, and web server reloaded) | |
24 | * | |
25 | * Settings: | |
26 | * - translation.mode: | |
27 | * - auto: use default setting (PHP implementation) | |
28 | * - php: use PHP implementation | |
29 | * - gettext: use gettext wrapper | |
30 | * - translation.language: | |
31 | * - auto: use autoLocale() and the language change according to user HTTP headers | |
32 | * - fixed language: e.g. 'fr' | |
33 | * - translation.extensions: | |
34 | * - domain => translation_path: allow plugins and themes to extend the defaut extension | |
35 | * The domain must be unique, and translation path must be relative, and contains the tree mentioned above. | |
36 | * | |
37 | * @package Shaarli | |
38 | */ | |
39 | class Languages | |
40 | { | |
41 | /** | |
42 | * Core translations domain | |
43 | */ | |
44 | const DEFAULT_DOMAIN = 'shaarli'; | |
45 | ||
46 | /** | |
47 | * @var TranslatorInterface | |
48 | */ | |
49 | protected $translator; | |
50 | ||
51 | /** | |
52 | * @var string | |
53 | */ | |
54 | protected $language; | |
55 | ||
56 | /** | |
57 | * @var ConfigManager | |
58 | */ | |
59 | protected $conf; | |
60 | ||
61 | /** | |
62 | * Languages constructor. | |
63 | * | |
64 | * @param string $language lang determined by autoLocale(), can be overridden. | |
65 | * @param ConfigManager $conf instance. | |
66 | */ | |
67 | public function __construct($language, $conf) | |
68 | { | |
69 | $this->conf = $conf; | |
70 | $confLanguage = $this->conf->get('translation.language', 'auto'); | |
71 | // Auto mode or invalid parameter, use the detected language. | |
72 | // If the detected language is invalid, it doesn't matter, it will use English. | |
73 | if ($confLanguage === 'auto' || ! $this->isValidLanguage($confLanguage)) { | |
74 | $this->language = substr($language, 0, 5); | |
75 | } else { | |
76 | $this->language = $confLanguage; | |
77 | } | |
78 | ||
79 | if (! extension_loaded('gettext') | |
80 | || in_array($this->conf->get('translation.mode', 'auto'), ['auto', 'php']) | |
81 | ) { | |
82 | $this->initPhpTranslator(); | |
83 | } else { | |
84 | $this->initGettextTranslator(); | |
85 | } | |
86 | ||
87 | // Register default functions (e.g. '__()') to use our Translator | |
88 | $this->translator->register(); | |
89 | } | |
90 | ||
91 | /** | |
92 | * Initialize the translator using php gettext extension (gettext dependency act as a wrapper). | |
93 | */ | |
94 | protected function initGettextTranslator() | |
95 | { | |
96 | $this->translator = new GettextTranslator(); | |
97 | $this->translator->setLanguage($this->language); | |
98 | $this->translator->loadDomain(self::DEFAULT_DOMAIN, 'inc/languages'); | |
99 | ||
100 | // Default extension translation from the current theme | |
101 | $themeTransFolder = rtrim($this->conf->get('raintpl_tpl'), '/') .'/'. $this->conf->get('theme') .'/language'; | |
102 | if (is_dir($themeTransFolder)) { | |
103 | $this->translator->loadDomain($this->conf->get('theme'), $themeTransFolder, false); | |
104 | } | |
105 | ||
106 | foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) { | |
107 | if ($domain !== self::DEFAULT_DOMAIN) { | |
108 | $this->translator->loadDomain($domain, $translationPath, false); | |
109 | } | |
110 | } | |
111 | } | |
112 | ||
113 | /** | |
114 | * Initialize the translator using a PHP implementation of gettext. | |
115 | * | |
116 | * Note that if language po file doesn't exist, errors are ignored (e.g. not installed language). | |
117 | */ | |
118 | protected function initPhpTranslator() | |
119 | { | |
120 | $this->translator = new Translator(); | |
121 | $translations = new Translations(); | |
122 | // Core translations | |
123 | try { | |
124 | $translations = $translations->addFromPoFile('inc/languages/'. $this->language .'/LC_MESSAGES/shaarli.po'); | |
125 | $translations->setDomain('shaarli'); | |
126 | $this->translator->loadTranslations($translations); | |
127 | } catch (\InvalidArgumentException $e) { | |
128 | } | |
129 | ||
130 | // Default extension translation from the current theme | |
131 | $theme = $this->conf->get('theme'); | |
132 | $themeTransFolder = rtrim($this->conf->get('raintpl_tpl'), '/') .'/'. $theme .'/language'; | |
133 | if (is_dir($themeTransFolder)) { | |
134 | try { | |
135 | $translations = Translations::fromPoFile( | |
136 | $themeTransFolder .'/'. $this->language .'/LC_MESSAGES/'. $theme .'.po' | |
137 | ); | |
138 | $translations->setDomain($theme); | |
139 | $this->translator->loadTranslations($translations); | |
140 | } catch (\InvalidArgumentException $e) { | |
141 | } | |
142 | } | |
143 | ||
144 | // Extension translations (plugins, themes, etc.). | |
145 | foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) { | |
146 | if ($domain === self::DEFAULT_DOMAIN) { | |
147 | continue; | |
148 | } | |
149 | ||
150 | try { | |
151 | $extension = Translations::fromPoFile( | |
152 | $translationPath . $this->language .'/LC_MESSAGES/'. $domain .'.po' | |
153 | ); | |
154 | $extension->setDomain($domain); | |
155 | $this->translator->loadTranslations($extension); | |
156 | } catch (\InvalidArgumentException $e) { | |
157 | } | |
158 | } | |
159 | } | |
160 | ||
161 | /** | |
162 | * Checks if a language string is valid. | |
163 | * | |
164 | * @param string $language e.g. 'fr' or 'en_US' | |
165 | * | |
166 | * @return bool true if valid, false otherwise | |
167 | */ | |
168 | protected function isValidLanguage($language) | |
169 | { | |
170 | return preg_match('/^[a-z]{2}(_[A-Z]{2})?/', $language) === 1; | |
171 | } | |
172 | ||
173 | /** | |
174 | * Get the list of available languages for Shaarli. | |
175 | * | |
176 | * @return array List of available languages, with their label. | |
177 | */ | |
178 | public static function getAvailableLanguages() | |
179 | { | |
180 | return [ | |
181 | 'auto' => t('Automatic'), | |
182 | 'en' => t('English'), | |
183 | 'fr' => t('French'), | |
184 | 'de' => t('German'), | |
185 | ]; | |
186 | } | |
187 | } |