language.php 10 KB
Newer Older
1
<?php
2
/**
3
 * @file include/language.php
4
 *
5
 * @brief Translation support.
6
7
8
 *
 * This file contains functions to work with translations and other
 * language related tasks.
9
10
 */

11
use CommerceGuys\Intl\Language\LanguageRepository;
12
13

/**
14
15
16
17
 * @brief Get the browser's submitted preferred languages.
 *
 * This functions parses the HTTP_ACCEPT_LANGUAGE header sent by the browser and
 * extracts the preferred languages and their priority.
18
19
20
 *
 * Get the language setting directly from system variables, bypassing get_config()
 * as database may not yet be configured.
21
 *
22
23
 * If possible, we use the value from the browser.
 *
24
 * @return array with ordered list of preferred languages from browser
25
26
 */
function get_browser_language() {
27
	$langs = array();
28
	$lang_parse = array();
29

30
	if (x($_SERVER, 'HTTP_ACCEPT_LANGUAGE')) {
31
		// break up string into pieces (languages and q factors)
32
		preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i',
33
34
			$_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse);

35
36
37
		if (count($lang_parse[1])) {
			// create a list like "en" => 0.8
			$langs = array_combine($lang_parse[1], $lang_parse[4]);
38

39
40
41
42
43
			// set default to 1 for any without q factor
			foreach ($langs as $lang => $val) {
				if ($val === '') $langs[$lang] = 1;
			}

44
			// sort list based on value
45
46
			arsort($langs, SORT_NUMERIC);
		}
47
	}
48
49

	return $langs;
friendica's avatar
.    
friendica committed
50
}
51

52
53
54
55
/**
 * @brief Returns the best language for which also a translation exists.
 *
 * This function takes the results from get_browser_language() and compares it
56
 * with the available translations and returns the best fitting language for
57
58
59
60
61
62
 * which there exists a translation.
 *
 * If there is no match fall back to config['system']['language']
 *
 * @return Language code in 2-letter ISO 639-1 (en).
 */
63
64
function get_best_language() {
	$langs = get_browser_language();
65
66
67

	if(isset($langs) && count($langs)) {
		foreach ($langs as $lang => $v) {
68
			$lang = strtolower($lang);
redmatrix's avatar
redmatrix committed
69
			if(is_dir("view/$lang")) {
70
71
72
73
74
75
				$preferred = $lang;
				break;
			}
		}
	}

redmatrix's avatar
redmatrix committed
76
77
78
79
80
81
82
83
84
	if(! isset($preferred))
		$preferred = 'unset';

	$arr = array('langs' => $langs, 'preferred' => $preferred);

	call_hooks('get_best_language',$arr);

	if($arr['preferred'] !== 'unset')
		return $arr['preferred'];
85

redmatrix's avatar
redmatrix committed
86
	return ((isset(App::$config['system']['language'])) ? App::$config['system']['language'] : 'en');
87
}
88
89
90
91


function push_lang($language) {

redmatrix's avatar
redmatrix committed
92
	App::$langsave = App::$language;
93

redmatrix's avatar
redmatrix committed
94
	if($language === App::$language)
95
96
		return;

redmatrix's avatar
redmatrix committed
97
98
	if(isset(App::$strings) && count(App::$strings)) {
		App::$stringsave = App::$strings;
99
	}
redmatrix's avatar
redmatrix committed
100
	App::$strings = array();
101
	load_translation_table($language);
redmatrix's avatar
redmatrix committed
102
	App::$language = $language;
103
104
105
106
}

function pop_lang() {

redmatrix's avatar
redmatrix committed
107
	if(App::$language === App::$langsave)
108
109
		return;

110
	if(isset(App::$stringsave) && is_array(App::$stringsave))
redmatrix's avatar
redmatrix committed
111
		App::$strings = App::$stringsave;
112
	else
redmatrix's avatar
redmatrix committed
113
		App::$strings = array();
114

redmatrix's avatar
redmatrix committed
115
	App::$language = App::$langsave;
116
117
}

118
119
120
121
122
123
/**
 * @brief Load string translation table for alternate language.
 *
 * @param string $lang language code in 2-letter ISO 639-1 (en, de, fr) format
 * @param boolean $install (optional) default false
 */
124
function load_translation_table($lang, $install = false) {
125

redmatrix's avatar
redmatrix committed
126
	App::$strings = array();
127
128
129
130
131

	if(file_exists("view/$lang/hstrings.php")) {
		include("view/$lang/hstrings.php");
	}
	elseif(file_exists("view/$lang/strings.php")) {
132
133
		include("view/$lang/strings.php");
	}
friendica's avatar
.    
friendica committed
134

135
	if(! $install) {
136
		$plugins = q("SELECT aname FROM addon WHERE installed=1;");
137
		if ($plugins !== false) {
138
			foreach($plugins as $p) {
139
				$name = $p['aname'];
140
141
142
143
				if(file_exists("addon/$name/lang/$lang/hstrings.php")) {
					include("addon/$name/lang/$lang/hstrings.php");
				}
				elseif(file_exists("addon/$name/lang/$lang/strings.php")) {
144
145
146
147
148
					include("addon/$name/lang/$lang/strings.php");
				}
			}
		}
	}
149
150
151
152

	// Allow individual strings to be over-ridden on this site
	// Either for the default language or for all languages

153
154
155
156
	if(file_exists("view/local-$lang/hstrings.php")) {
		include("view/local-$lang/hstrings.php");
	}
	elseif(file_exists("view/local-$lang/strings.php")) {
157
158
		include("view/local-$lang/strings.php");
	}
friendica's avatar
.    
friendica committed
159
}
160

161
/**
162
 * @brief Translate string if translation exists.
163
 *
164
 * @param string $s string that should get translated
165
 * @param string $ctx (optional) context to appear in po file
166
 * @return translated string if exists, otherwise return $s
167
 *
168
 */
169
function t($s, $ctx = '') {
170

171
	$cs = $ctx ? '__ctx:' . $ctx . '__ ' . $s : $s;
redmatrix's avatar
redmatrix committed
172
173
	if (x(App::$strings, $cs)) {
		$t = App::$strings[$cs];
174

175
		return ((is_array($t)) ? translate_projectname($t[0]) : translate_projectname($t));
176
	}
177

178
	return translate_projectname($s);
friendica's avatar
.    
friendica committed
179
180
}

181
182
183
184
185
/**
 * @brief translate product name
 *  Merging strings from different project names is problematic so we'll do that with a string replacement
 */

186
function translate_projectname($s) {
187

188
	return str_replace(array('$projectname','$Projectname'),array(Zotlabs\Lib\System::get_platform_name(),ucfirst(Zotlabs\Lib\System::get_platform_name())),$s);
189
190
191
192
193

}



194
195
196
197
198
199
200
201
202
/**
 * @brief
 *
 * @param string $singular
 * @param string $plural
 * @param int $count
 * @param string $ctx
 * @return string
 */
203
function tt($singular, $plural, $count, $ctx = ''){
204

205
	$cs = $ctx ? "__ctx:" . $ctx . "__ " . $singular : $singular;
redmatrix's avatar
redmatrix committed
206
207
208
	if (x(App::$strings,$cs)) {
		$t = App::$strings[$cs];
		$f = 'string_plural_select_' . str_replace('-', '_', App::$language);
209
		if (! function_exists($f))
210
			$f = 'string_plural_select_default';
211

212
		$k = $f($count);
213

214
		return is_array($t) ? $t[$k] : $t;
215
	}
216
217

	if ($count != 1) {
218
219
220
221
		return $plural;
	} else {
		return $singular;
	}
friendica's avatar
.    
friendica committed
222
}
223

224
225
226
227
228
229
230
/**
 * @brief Provide a fallback which will not collide with a function defined in
 * any language file.
 *
 * @param int $n
 * @return boolean
 */
231
232
function string_plural_select_default($n) {
	return ($n != 1);
friendica's avatar
.    
friendica committed
233
}
234

235
236
237
238
239
240
241
242
243
244
245
/**
 * @brief Takes a string and tries to identify the language.
 *
 * It uses the pear library Text_LanguageDetect and it can identify 52 human languages.
 * It returns the identified languges and a confidence score for each.
 *
 * Strings need to have a min length config['system']['language_detect_min_length']
 * and you can influence the confidence that must be met before a result will get
 * returned through config['system']['language_detect_min_confidence'].
 *
 * @see http://pear.php.net/package/Text_LanguageDetect
246
 * @param string $s A string to examine
247
 * @return string Language code in 2-letter ISO 639-1 (en, de, fr) format
248
 */
249
function detect_language($s) {
250
	$min_length = get_config('system', 'language_detect_min_length');
251
	if ($min_length === false)
252
253
		$min_length = LANGUAGE_DETECT_MIN_LENGTH;

254
	$min_confidence = get_config('system', 'language_detect_min_confidence');
255
	if ($min_confidence === false)
256
257
		$min_confidence = LANGUAGE_DETECT_MIN_CONFIDENCE;

258
	// embedded apps have long base64 strings which will trip up the detector.
259
	$naked_body = preg_replace('/\[app\](.*?)\[\/app\]/', '', $s);
260
	// strip off bbcode
261
	$naked_body = preg_replace('/\[(.+?)\]/', '', $naked_body);
262
	if (mb_strlen($naked_body) < intval($min_length)) {
263
		logger('string length less than ' . intval($min_length), LOGGER_DATA);
264
		return '';
265
	}
266
267

	$l = new Text_LanguageDetect;
268
269
270
271
272
273
274
275
	try {
		// return 2-letter ISO 639-1 (en) language code
		$l->setNameMode(2);
		$lng = $l->detectConfidence($naked_body);
		logger('detect language: ' . print_r($lng, true) . $naked_body, LOGGER_DATA);
	} catch (Text_LanguageDetect_Exception $e) {
		logger('detect language exception: ' . $e->getMessage(), LOGGER_DATA);
	}
276

277
	if ((! $lng) || (! (x($lng,'language')))) {
278
279
280
		return '';
	}

281
	if ($lng['confidence'] < (float) $min_confidence) {
282
283
284
285
		logger('detect language: confidence less than ' . (float) $min_confidence, LOGGER_DATA);
		return '';
	}

286
287
288
289
290
291
292
293
294
	return($lng['language']);
}

/**
 * @brief Returns the display name of a given language code.
 *
 * By default we use the localized language name. You can switch the result
 * to any language with the optional 2nd parameter $l.
 *
295
 * $s and $l should be in 2-letter ISO 639-1 format.
296
297
298
 *
 * If nothing could be looked up it returns $s.
 *
299
300
 * @param string $s Language code to look up
 * @param string $l (optional) In which language to return the name
301
302
303
 * @return string with the language name, or $s if unrecognized
 */
function get_language_name($s, $l = null) {
304
	// get() expects the second part to be in upper case
305
306
	if (strpos($s, '-') !== false) $s = substr($s, 0, 2) . strtoupper(substr($s, 2));
	if ($l !== null && strpos($l, '-') !== false) $l = substr($l, 0, 2) . strtoupper(substr($l, 2));
307

308
309
310
311
312
	$languageRepository = new LanguageRepository;

	// Sometimes intl doesn't like the second part at all ...
	try {
		$language = $languageRepository->get($s, $l);
313
314
315
	} catch(CommerceGuys\Intl\Exception\UnknownLanguageException $e) {
		$s = substr($s, 0, 2);
		if($l !== null) $l = substr($s, 0, 2);
316
317
		try {
			$language = $languageRepository->get($s, $l);
318
		} catch (CommerceGuys\Intl\Exception\UnknownLanguageException $e) {
319
			return $s; // Give up
320
321
		} catch (CommerceGuys\Intl\Exception\UnknownLocaleException $e) {
			return $s; // Give up
322
		}
323
	}
324
325

	return $language->getName();
326
}
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378



function language_list() {

	$langs = glob('view/*/hstrings.php');

	$lang_options = array();
	$selected = "";

	if(is_array($langs) && count($langs)) {
		if(! in_array('view/en/hstrings.php',$langs))
			$langs[] = 'view/en/';
		asort($langs);
		foreach($langs as $l) {
			$ll = substr($l,5);
			$ll = substr($ll,0,strrpos($ll,'/'));
			$lang_options[$ll] = get_language_name($ll, $ll) . " ($ll)";
		}
	}
	return $lang_options;
}

function lang_selector() {

	$langs = glob('view/*/hstrings.php');

	$lang_options = array();
	$selected = "";

	if(is_array($langs) && count($langs)) {
		$langs[] = '';
		if(! in_array('view/en/hstrings.php',$langs))
			$langs[] = 'view/en/';
		asort($langs);
		foreach($langs as $l) {
			if($l == '') {
				$lang_options[""] = t('default');
				continue;
			}
			$ll = substr($l,5);
			$ll = substr($ll,0,strrpos($ll,'/'));
			$selected = (($ll === App::$language && (x($_SESSION, 'language'))) ? $ll : $selected);
			$lang_options[$ll] = get_language_name($ll, $ll) . " ($ll)";
		}
	}

	$tpl = get_markup_template('lang_selector.tpl');

	$o = replace_macros($tpl, array(
		'$title' => t('Select an alternate language'),
		'$langs' => array($lang_options, $selected),
379

380
381
382
383
384
	));

	return $o;
}