]>
Commit | Line | Data |
---|---|---|
edf3ff5a A |
1 | <?php |
2 | ||
12266213 A |
3 | namespace Shaarli; |
4 | ||
5 | use Gettext\GettextTranslator; | |
12266213 A |
6 | use Gettext\Translations; |
7 | use Gettext\Translator; | |
8 | use Gettext\TranslatorInterface; | |
9 | use Shaarli\Config\ConfigManager; | |
10 | ||
edf3ff5a | 11 | /** |
12266213 A |
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(). | |
edf3ff5a | 17 | * |
12266213 A |
18 | * Translation files PO/MO files follow gettext standard and must be placed under: |
19 | * <translation path>/<language>/LC_MESSAGES/<domain>.[po|mo] | |
edf3ff5a | 20 | * |
12266213 A |
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) | |
edf3ff5a | 24 | * |
12266213 A |
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 | |
edf3ff5a | 38 | */ |
12266213 A |
39 | class Languages |
40 | { | |
41 | /** | |
42 | * Core translations domain | |
43 | */ | |
b99e00f7 | 44 | public const DEFAULT_DOMAIN = 'shaarli'; |
12266213 A |
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 | * | |
f39580c6 | 64 | * @param string $language lang determined by autoLocale(), can be overridden. |
12266213 A |
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'); | |
b7c412d4 A |
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. | |
12266213 A |
73 | if ($confLanguage === 'auto' || ! $this->isValidLanguage($confLanguage)) { |
74 | $this->language = substr($language, 0, 5); | |
75 | } else { | |
76 | $this->language = $confLanguage; | |
77 | } | |
78 | ||
53054b2b A |
79 | if ( |
80 | ! extension_loaded('gettext') | |
12266213 A |
81 | || in_array($this->conf->get('translation.mode', 'auto'), ['auto', 'php']) |
82 | ) { | |
83 | $this->initPhpTranslator(); | |
84 | } else { | |
85 | $this->initGettextTranslator(); | |
86 | } | |
87 | ||
88 | // Register default functions (e.g. '__()') to use our Translator | |
89 | $this->translator->register(); | |
90 | } | |
91 | ||
92 | /** | |
93 | * Initialize the translator using php gettext extension (gettext dependency act as a wrapper). | |
94 | */ | |
f211e417 | 95 | protected function initGettextTranslator() |
12266213 A |
96 | { |
97 | $this->translator = new GettextTranslator(); | |
98 | $this->translator->setLanguage($this->language); | |
99 | $this->translator->loadDomain(self::DEFAULT_DOMAIN, 'inc/languages'); | |
100 | ||
68c6afc5 | 101 | // Default extension translation from the current theme |
53054b2b | 102 | $themeTransFolder = rtrim($this->conf->get('raintpl_tpl'), '/') . '/' . $this->conf->get('theme') . '/language'; |
68c6afc5 A |
103 | if (is_dir($themeTransFolder)) { |
104 | $this->translator->loadDomain($this->conf->get('theme'), $themeTransFolder, false); | |
105 | } | |
106 | ||
12266213 A |
107 | foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) { |
108 | if ($domain !== self::DEFAULT_DOMAIN) { | |
109 | $this->translator->loadDomain($domain, $translationPath, false); | |
110 | } | |
111 | } | |
112 | } | |
113 | ||
114 | /** | |
115 | * Initialize the translator using a PHP implementation of gettext. | |
116 | * | |
117 | * Note that if language po file doesn't exist, errors are ignored (e.g. not installed language). | |
118 | */ | |
119 | protected function initPhpTranslator() | |
120 | { | |
121 | $this->translator = new Translator(); | |
122 | $translations = new Translations(); | |
123 | // Core translations | |
124 | try { | |
b99e00f7 A |
125 | $translations = $translations->addFromPoFile( |
126 | 'inc/languages/' . $this->language . '/LC_MESSAGES/shaarli.po' | |
127 | ); | |
12266213 A |
128 | $translations->setDomain('shaarli'); |
129 | $this->translator->loadTranslations($translations); | |
f211e417 V |
130 | } catch (\InvalidArgumentException $e) { |
131 | } | |
12266213 | 132 | |
68c6afc5 A |
133 | // Default extension translation from the current theme |
134 | $theme = $this->conf->get('theme'); | |
53054b2b | 135 | $themeTransFolder = rtrim($this->conf->get('raintpl_tpl'), '/') . '/' . $theme . '/language'; |
68c6afc5 A |
136 | if (is_dir($themeTransFolder)) { |
137 | try { | |
138 | $translations = Translations::fromPoFile( | |
53054b2b | 139 | $themeTransFolder . '/' . $this->language . '/LC_MESSAGES/' . $theme . '.po' |
68c6afc5 A |
140 | ); |
141 | $translations->setDomain($theme); | |
142 | $this->translator->loadTranslations($translations); | |
f211e417 V |
143 | } catch (\InvalidArgumentException $e) { |
144 | } | |
68c6afc5 | 145 | } |
12266213 A |
146 | |
147 | // Extension translations (plugins, themes, etc.). | |
148 | foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) { | |
149 | if ($domain === self::DEFAULT_DOMAIN) { | |
150 | continue; | |
151 | } | |
152 | ||
153 | try { | |
9d9f6d75 | 154 | $extension = Translations::fromPoFile( |
53054b2b | 155 | $translationPath . $this->language . '/LC_MESSAGES/' . $domain . '.po' |
9d9f6d75 | 156 | ); |
12266213 A |
157 | $extension->setDomain($domain); |
158 | $this->translator->loadTranslations($extension); | |
f211e417 V |
159 | } catch (\InvalidArgumentException $e) { |
160 | } | |
12266213 A |
161 | } |
162 | } | |
163 | ||
164 | /** | |
165 | * Checks if a language string is valid. | |
166 | * | |
167 | * @param string $language e.g. 'fr' or 'en_US' | |
168 | * | |
169 | * @return bool true if valid, false otherwise | |
170 | */ | |
171 | protected function isValidLanguage($language) | |
172 | { | |
173 | return preg_match('/^[a-z]{2}(_[A-Z]{2})?/', $language) === 1; | |
edf3ff5a | 174 | } |
f39580c6 A |
175 | |
176 | /** | |
177 | * Get the list of available languages for Shaarli. | |
178 | * | |
179 | * @return array List of available languages, with their label. | |
180 | */ | |
181 | public static function getAvailableLanguages() | |
182 | { | |
183 | return [ | |
184 | 'auto' => t('Automatic'), | |
ebc027ec | 185 | 'de' => t('German'), |
f39580c6 A |
186 | 'en' => t('English'), |
187 | 'fr' => t('French'), | |
ebc027ec | 188 | 'jp' => t('Japanese'), |
150f2a0f | 189 | 'ru' => t('Russian'), |
f39580c6 A |
190 | ]; |
191 | } | |
edf3ff5a | 192 | } |