]>
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 | public 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 ( | |
80 | ! extension_loaded('gettext') | |
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 | */ | |
95 | protected function initGettextTranslator() | |
96 | { | |
97 | $this->translator = new GettextTranslator(); | |
98 | $this->translator->setLanguage($this->language); | |
99 | $this->translator->loadDomain(self::DEFAULT_DOMAIN, 'inc/languages'); | |
100 | ||
101 | // Default extension translation from the current theme | |
102 | $themeTransFolder = rtrim($this->conf->get('raintpl_tpl'), '/') . '/' . $this->conf->get('theme') . '/language'; | |
103 | if (is_dir($themeTransFolder)) { | |
104 | $this->translator->loadDomain($this->conf->get('theme'), $themeTransFolder, false); | |
105 | } | |
106 | ||
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 { | |
125 | $translations = $translations->addFromPoFile( | |
126 | 'inc/languages/' . $this->language . '/LC_MESSAGES/shaarli.po' | |
127 | ); | |
128 | $translations->setDomain('shaarli'); | |
129 | $this->translator->loadTranslations($translations); | |
130 | } catch (\InvalidArgumentException $e) { | |
131 | } | |
132 | ||
133 | // Default extension translation from the current theme | |
134 | $theme = $this->conf->get('theme'); | |
135 | $themeTransFolder = rtrim($this->conf->get('raintpl_tpl'), '/') . '/' . $theme . '/language'; | |
136 | if (is_dir($themeTransFolder)) { | |
137 | try { | |
138 | $translations = Translations::fromPoFile( | |
139 | $themeTransFolder . '/' . $this->language . '/LC_MESSAGES/' . $theme . '.po' | |
140 | ); | |
141 | $translations->setDomain($theme); | |
142 | $this->translator->loadTranslations($translations); | |
143 | } catch (\InvalidArgumentException $e) { | |
144 | } | |
145 | } | |
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 { | |
154 | $extension = Translations::fromPoFile( | |
155 | $translationPath . $this->language . '/LC_MESSAGES/' . $domain . '.po' | |
156 | ); | |
157 | $extension->setDomain($domain); | |
158 | $this->translator->loadTranslations($extension); | |
159 | } catch (\InvalidArgumentException $e) { | |
160 | } | |
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; | |
174 | } | |
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'), | |
185 | 'de' => t('German'), | |
186 | 'en' => t('English'), | |
187 | 'fr' => t('French'), | |
188 | 'jp' => t('Japanese'), | |
189 | ]; | |
190 | } | |
191 | } |