1<?php
2# MantisBT - A PHP based bugtracking system
3
4# MantisBT is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation, either version 2 of the License, or
7# (at your option) any later version.
8#
9# MantisBT is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with MantisBT.  If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Language (Internationalization) API
19 *
20 * @package CoreAPI
21 * @subpackage LanguageAPI
22 * @copyright Copyright 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
23 * @copyright Copyright 2002  MantisBT Team - mantisbt-dev@lists.sourceforge.net
24 * @link http://www.mantisbt.org
25 *
26 * @uses authentication_api.php
27 * @uses config_api.php
28 * @uses constant_inc.php
29 * @uses error_api.php
30 * @uses plugin_api.php
31 * @uses user_pref_api.php
32 */
33
34require_api( 'authentication_api.php' );
35require_api( 'config_api.php' );
36require_api( 'constant_inc.php' );
37require_api( 'error_api.php' );
38require_api( 'plugin_api.php' );
39require_api( 'user_pref_api.php' );
40
41# Cache of localization strings in the language specified by the last
42# lang_load call
43$g_lang_strings = array();
44
45# stack for language overrides
46$g_lang_overrides = array();
47
48# To be used in custom_strings_inc.php :
49$g_active_language = '';
50
51/**
52 * Loads the specified language and stores it in $g_lang_strings, to be used by lang_get
53 * @param string $p_lang Name of Language to load.
54 * @param string $p_dir  Directory Containing language file.
55 * @return void
56 */
57function lang_load( $p_lang, $p_dir = null ) {
58	global $g_lang_strings, $g_active_language;
59
60	$g_active_language = $p_lang;
61	if( isset( $g_lang_strings[$p_lang] ) && is_null( $p_dir ) ) {
62		return;
63	}
64
65	if( !lang_language_exists( $p_lang ) ) {
66		return;
67	}
68
69	if( $p_dir === null ) {
70		include_once( config_get_global( 'language_path' ) . 'strings_' . $p_lang . '.txt' );
71	} else {
72		if( is_file( $p_dir . 'strings_' . $p_lang . '.txt' ) ) {
73			include_once( $p_dir . 'strings_' . $p_lang . '.txt' );
74		}
75	}
76
77	# Allow overriding strings declared in the language file.
78	# custom_strings_inc.php can use $g_active_language.
79	# Include file multiple times to allow for overrides per language.
80	global $g_config_path;
81
82	if( file_exists( $g_config_path . 'custom_strings_inc.php' ) ) {
83		include( $g_config_path . 'custom_strings_inc.php' );
84	}
85
86	$t_vars = get_defined_vars();
87
88	foreach( array_keys( $t_vars ) as $t_var ) {
89		$t_lang_var = preg_replace( '/^s_/', '', $t_var );
90		if( $t_lang_var != $t_var ) {
91			$g_lang_strings[$p_lang][$t_lang_var] = $$t_var;
92		} else if( 'MANTIS_ERROR' == $t_var ) {
93			if( isset( $g_lang_strings[$p_lang][$t_lang_var] ) ) {
94				foreach( $$t_var as $t_key => $t_val ) {
95					$g_lang_strings[$p_lang][$t_lang_var][$t_key] = $t_val;
96				}
97			} else {
98				$g_lang_strings[$p_lang][$t_lang_var] = $$t_var;
99			}
100		}
101	}
102}
103
104/**
105 * Determine the preferred language
106 * @return string
107 */
108function lang_get_default() {
109	global $g_active_language;
110
111	$t_lang = false;
112
113	# Confirm that the user's language can be determined
114	if( function_exists( 'auth_is_user_authenticated' ) && auth_is_user_authenticated() ) {
115		$t_lang = user_pref_get_language( auth_get_current_user_id() );
116	}
117
118	# Otherwise fall back to default
119	if( !$t_lang ) {
120		$t_lang = config_get_global( 'default_language' );
121	}
122
123	if( $t_lang == 'auto' ) {
124		$t_lang = lang_map_auto();
125	}
126
127	# Remember the language
128	$g_active_language = $t_lang;
129
130	return $t_lang;
131}
132
133/**
134 * Auto Map Language from HTTP server data
135 * @return string
136 */
137function lang_map_auto() {
138	$t_lang = config_get_global( 'fallback_language' );
139
140	if( isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) {
141		$t_accept_langs = explode( ',', $_SERVER['HTTP_ACCEPT_LANGUAGE'] );
142		$t_auto_map = config_get_global( 'language_auto_map' );
143
144		# Expand language map
145		$t_auto_map_exp = array();
146		foreach( $t_auto_map as $t_encs => $t_enc_lang ) {
147			$t_encs_arr = explode( ',', $t_encs );
148
149			foreach( $t_encs_arr as $t_enc ) {
150				$t_auto_map_exp[trim( $t_enc )] = $t_enc_lang;
151			}
152		}
153
154		# Find encoding
155		foreach( $t_accept_langs as $t_accept_lang ) {
156			$t_tmp = explode( ';', mb_strtolower( $t_accept_lang ) );
157
158			if( isset( $t_auto_map_exp[trim( $t_tmp[0] )] ) ) {
159				$t_valid_langs = config_get( 'language_choices_arr' );
160				$t_found_lang = $t_auto_map_exp[trim( $t_tmp[0] )];
161
162				if( in_array( $t_found_lang, $t_valid_langs, true ) ) {
163					$t_lang = $t_found_lang;
164					break;
165				}
166			}
167		}
168	}
169
170	return $t_lang;
171}
172
173/**
174 * Ensures that a language file has been loaded.
175 * Also load the currently active plugin's strings, if there is one.
176 * @param string $p_lang The language name.
177 * @return void
178 */
179function lang_ensure_loaded( $p_lang ) {
180	global $g_lang_strings;
181
182	if( !isset( $g_lang_strings[$p_lang] ) ) {
183		lang_load( $p_lang );
184	}
185
186	# Load current plugin's strings
187	$t_plugin_current = plugin_get_current();
188	if( $t_plugin_current !== null ) {
189		lang_load( $p_lang, config_get_global( 'plugin_path' ) . $t_plugin_current . '/lang/' );
190	}
191}
192
193/**
194* Check if the given language exists
195*
196* @param string $p_lang The language name.
197* @return boolean
198*/
199function lang_language_exists( $p_lang ) {
200	$t_valid_langs = config_get( 'language_choices_arr' );
201	$t_valid = in_array( $p_lang, $t_valid_langs, true );
202	return $t_valid;
203}
204
205/**
206 * language stack implementation
207 * push a language onto the stack
208 * @param string $p_lang The language name.
209 * @return void
210 */
211function lang_push( $p_lang = null ) {
212	global $g_lang_overrides;
213
214	# If no specific language is requested, we'll
215	#  try to determine the language from the users
216	#  preferences
217
218	$t_lang = $p_lang;
219
220	if( null === $t_lang ) {
221		$t_lang = config_get_global( 'default_language' );
222	}
223
224	# don't allow 'auto' as a language to be pushed onto the stack
225	#  The results from auto are always the local user, not what the
226	#  override wants, unless this is the first language setting
227	if( ( 'auto' == $t_lang ) && ( 0 < count( $g_lang_overrides ) ) ) {
228		$t_lang = config_get_global( 'fallback_language' );
229	}
230
231	$g_lang_overrides[] = $t_lang;
232
233	# Remember the language
234	$g_active_language = $t_lang;
235
236	# make sure it's loaded
237	lang_ensure_loaded( $t_lang );
238}
239
240/**
241 * pop a language from the stack and return it
242 * @return string
243 */
244function lang_pop() {
245	global $g_lang_overrides;
246
247	return array_pop( $g_lang_overrides );
248}
249
250/**
251 * return value on top of the language stack
252 * return default if stack is empty
253 * @return string
254 */
255function lang_get_current() {
256	global $g_lang_overrides;
257
258	$t_count_overrides = count( $g_lang_overrides );
259	if( $t_count_overrides > 0 ) {
260		$t_lang = $g_lang_overrides[$t_count_overrides - 1];
261	} else {
262		$t_lang = lang_get_default();
263	}
264
265	return $t_lang;
266}
267
268/**
269 * Retrieves an internationalized string
270 * This function will return one of (in order of preference):
271 *  1. The string in the current user's preferred language (if defined)
272 *  2. The string in English
273 * Note: when $p_string is 'MANTIS_ERROR', the return value is an array of error messages.
274 * @param string $p_string The language string to retrieve.
275 * @param string $p_lang   The language name.
276 * @return string|array
277 */
278function lang_get( $p_string, $p_lang = null ) {
279	global $g_lang_strings;
280
281	# If no specific language is requested, we'll try to
282	# determine the language from the users preferences
283
284	$t_lang = $p_lang;
285
286	if( null === $t_lang ) {
287		$t_lang = lang_get_current();
288	}
289
290	# Now we'll make sure that the requested language is loaded
291	lang_ensure_loaded( $t_lang );
292
293	# note in the current implementation we always return the same value
294	#  because we don't have a concept of falling back on a language.  The
295	#  language files actually *contain* English strings if none has been
296	#  defined in the correct language
297	# @todo thraxisp - not sure if this is still true. Strings from last language loaded
298	#      may still be in memory if a new language is loaded.
299
300	if( lang_exists( $p_string, $t_lang ) ) {
301		return $g_lang_strings[$t_lang][$p_string];
302	} elseif( $t_lang != 'english' ) {
303		# If the string was not found in the foreign language, then retry with English.
304		return lang_get( $p_string, 'english' );
305	} else {
306		error_parameters( $p_string );
307		trigger_error( ERROR_LANG_STRING_NOT_FOUND, WARNING );
308		return $p_string;
309	}
310}
311
312/**
313 * Check the language entry, if found return true, otherwise return false.
314 * @param string $p_string The language string to retrieve.
315 * @param string $p_lang   The language name.
316 * @return boolean
317 */
318function lang_exists( $p_string, $p_lang ) {
319	global $g_lang_strings;
320
321	return( isset( $g_lang_strings[$p_lang] ) && isset( $g_lang_strings[$p_lang][$p_string] ) );
322}
323
324/**
325 * Get language:
326 * - If found, return the appropriate string (as lang_get()).
327 * - If not found, no default supplied, return the supplied string as is.
328 * - If not found, default supplied, return default.
329 * @param string $p_string  The language string to retrieve.
330 * @param string $p_default The default value to return.
331 * @param string $p_lang    The language name.
332 * @return string
333 */
334function lang_get_defaulted( $p_string, $p_default = null, $p_lang = null ) {
335	$t_lang = $p_lang;
336
337	if( null === $t_lang ) {
338		$t_lang = lang_get_current();
339	}
340
341	# Now we'll make sure that the requested language is loaded
342	lang_ensure_loaded( $t_lang );
343
344	if( lang_exists( $p_string, $t_lang ) ) {
345		return lang_get( $p_string );
346	} elseif( $t_lang != 'english' ) {
347		# If the string was not found in the foreign language, then retry with English.
348		return lang_get_defaulted( $p_string, $p_default, 'english' );
349	} else {
350		# English string was not found either, return the default,
351		# or the original string if no default was provided
352		return $p_default !== null ? $p_default : $p_string;
353	}
354}
355
356/**
357 * Maps current lang string to moment.js locale.
358 * @see https://github.com/moment/moment/tree/develop/locale
359 * @return string Two chars browser language code (e.g. 'de' for German)
360 */
361function lang_get_current_datetime_locale() {
362	$t_lang = lang_get_current();
363
364	# Lookup $g_language_auto_map by value and then return the first key
365	$t_auto_map = config_get_global( 'language_auto_map' );
366	$t_entry = array_search( $t_lang, $t_auto_map );
367	$t_key_arr = explode( ',', $t_entry );
368
369	return $t_key_arr[0];
370}
371