1<?php
2/*
3** Zabbix
4** Copyright (C) 2001-2021 Zabbix SIA
5**
6** This program is free software; you can redistribute it and/or modify
7** it under the terms of the GNU General Public License as published by
8** the Free Software Foundation; either version 2 of the License, or
9** (at your option) any later version.
10**
11** This program is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU General Public License
17** along with this program; if not, write to the Free Software
18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19**/
20
21
22/**
23 * In case gettext functions do not exist, just replacing them with our own,
24 * so user can see at least English translation.
25 */
26if (!function_exists('_')) {
27	/**
28	 * Stub gettext function in case gettext is not available.
29	 *
30	 * @param string $string
31	 *
32	 * @return string
33	 */
34	function _($string) {
35		return $string;
36	}
37}
38
39if (!function_exists('ngettext')) {
40	/**
41	 * Stub gettext function in case gettext is not available. Do not use directly, use _n() instead.
42	 *
43	 * @see _n
44	 *
45	 * @param string $string1
46	 * @param string $string2
47	 * @param string $n
48	 *
49	 * @return string
50	 */
51	function ngettext($string1, $string2, $n) {
52		return ($n == 1) ? $string1 : $string2;
53	}
54}
55
56/**
57 * Translates the string with respect to the given context.
58 *
59 * @see _x
60 *
61 * @param string $context
62 * @param string $msgId
63 *
64 * @return string
65 */
66function pgettext($context, $msgId) {
67	$contextString = $context."\004".$msgId;
68	$translation = _($contextString);
69
70	return ($translation == $contextString) ? $msgId : $translation;
71}
72
73/**
74 * Translates the string with respect to the given context and plural forms.
75 *
76 * @see _xn
77 *
78 * @param string $context
79 * @param string $msgId
80 * @param string $msgIdPlural
81 * @param string $num
82 *
83 * @return string
84 */
85function npgettext($context, $msgId, $msgIdPlural, $num) {
86	$contextString = $context."\004".$msgId;
87	$contextStringp = $context."\004".$msgIdPlural;
88	return ngettext($contextString, $contextStringp, $num);
89}
90
91/**
92 * Translates the string and substitutes the placeholders with the given parameters.
93 * Placeholders must be defined as %1$s, %2$s etc.
94 *
95 * @param string $string         String optionally containing placeholders to substitute.
96 * @param string $param,...      Unlimited number of optional parameters to replace sequential placeholders.
97 *
98 * @return string
99 */
100function _s($string) {
101	$arguments = array_slice(func_get_args(), 1);
102
103	return _params(_($string), $arguments);
104}
105
106/**
107 * Translates the string in the correct form with respect to the given numeric parameter. According to gettext
108 * standards the numeric parameter must be passed last.
109 * Supports unlimited parameters; placeholders must be defined as %1$s, %2$s etc.
110 *
111 * Examples:
112 * _n('%2$s item on host %1$s', '%2$s items on host %1$s', 'Zabbix server', 1) // 1 item on host Zabbix server
113 * _n('%2$s item on host %1$s', '%2$s items on host %1$s', 'Zabbix server', 2) // 2 items on host Zabbix server
114 *
115 * @param string $string1		singular string
116 * @param string $string2		plural string
117 * @param string $param			parameter to replace the first placeholder
118 * @param string $param,...		unlimited number of optional parameters
119 *
120 * @return string
121 */
122function _n($string1, $string2) {
123	$arguments = array_slice(func_get_args(), 2);
124
125	return _params(ngettext($string1, $string2, end($arguments)), $arguments);
126}
127
128/**
129 * Translates the string with respect to the given context.
130 * If no translation is found, the original string will be used.
131 *
132 * Example: _x('Message', 'context');
133 * returns: 'Message'
134 *
135 * @param string $message		string to translate
136 * @param string $context		context of the string
137 *
138 * @return string
139 */
140function _x($message, $context) {
141	return ($context == '')
142		? _($message)
143		: pgettext($context, $message);
144}
145
146/**
147 * Translates the string with respect to the given context and replaces placeholders with supplied arguments.
148 * If no translation is found, the original string will be used. Unlimited number of parameters supplied.
149 * Parameter placeholders must be defined as %1$s, %2$s etc.
150 *
151 * Example: _xs('Message for arg1 "%1$s" and arg2 "%2$s"', 'context', 'arg1Value', 'arg2Value');
152 * returns: 'Message for arg1 "arg1Value" and arg2 "arg2Value"'
153 *
154 * @param string $message       String to translate.
155 * @param string $context       Context of the string.
156 * @param string $param,...     Unlimited number of optional parameters to replace sequential placeholders.
157 *
158 * @return string
159 */
160function _xs($message, $context) {
161	$arguments = array_slice(func_get_args(), 2);
162
163	return ($context == '')
164		? _params($message, $arguments)
165		: _params(pgettext($context, $message), $arguments);
166}
167
168/**
169 * Translates the string with respect to the given context and plural forms, also replaces placeholders with supplied
170 * arguments. If no translation is found, the original string will be used. Unlimited number of parameters supplied.
171 *
172 * Parameter placeholders must be defined as %1$s, %2$s etc.
173 *
174 * Example: _xn('%1$s message for arg1 "%2$s"', '%1$s messages for arg1 "%2$s"', 3, 'context', 'arg1Value');
175 * returns: '3 messages for arg1 "arg1Value"'
176 *
177 * @param string $message           String to translate.
178 * @param string $messagePlural     String to translate for plural form.
179 * @param int    $num               Number to determine usage of plural form, also is used as first replace argument.
180 * @param string $context           Context of the string.
181 * @param string $param,...         Unlimited number of optional parameters to replace sequential placeholders.
182 *
183 * @return string
184 */
185function _xn($message, $messagePlural, $num, $context) {
186	$arguments = array_slice(func_get_args(), 4);
187	array_unshift($arguments, $num);
188
189	return _params(pgettext($context, ngettext($message, $messagePlural, $num)), $arguments);
190}
191
192/**
193 * Returns a formatted string.
194 *
195 * @param string $format		receives already stranlated string with format
196 * @param array  $arguments		arguments to replace according to given format
197 *
198 * @return string
199 */
200function _params($format, array $arguments) {
201	return vsprintf($format, $arguments);
202}
203
204/**
205 * Initialize locale environment and gettext translations depending on language selected by user.
206 *
207 * Note: should be called before including file includes/translateDefines.inc.php.
208 *
209 * @param string $language    Locale language prefix like en_US, ru_RU etc.
210 * @param string $error       Message on failure.
211 *
212 * @return bool    Whether locale could be switched; always true for en_GB.
213 */
214function setupLocale(string $language, ?string &$error = ''): bool {
215	$numeric_locales = [
216		'C', 'POSIX', 'en', 'en_US', 'en_US.UTF-8', 'English_United States.1252', 'en_GB', 'en_GB.UTF-8'
217	];
218	$locale_variants = zbx_locale_variants($language);
219	$locale_set = false;
220	$error = '';
221
222	init_mbstrings();
223
224	// Since LC_MESSAGES may be unavailable on some systems, try to set all of the locales and then make adjustments.
225	foreach ($locale_variants as $locale) {
226		putenv('LC_ALL='.$locale);
227		putenv('LANG='.$locale);
228		putenv('LANGUAGE='.$locale);
229
230		if (setlocale(LC_ALL, $locale)) {
231			$locale_set = true;
232			break;
233		}
234	}
235
236	// Force PHP to always use a point instead of a comma for decimal numbers.
237	setlocale(LC_NUMERIC, $numeric_locales);
238
239	if (function_exists('bindtextdomain')) {
240		bindtextdomain('frontend', 'locale');
241		bind_textdomain_codeset('frontend', 'UTF-8');
242		textdomain('frontend');
243	}
244
245	if (!$locale_set && strtolower($language) !== 'en_gb') {
246		$language = htmlspecialchars($language, ENT_QUOTES, 'UTF-8');
247		$locale_variants = array_map(function ($locale) {
248			return htmlspecialchars($locale, ENT_QUOTES, 'UTF-8');
249		}, $locale_variants);
250
251		$error = 'Locale for language "'.$language.'" is not found on the web server. Tried to set: '.
252			implode(', ', $locale_variants).'. Unable to translate Zabbix interface.';
253	}
254
255	return ($error === '');
256}
257