Move methods to the factory that are not related to translating, but to guessing/finding the languagetags/v9.0beta1
@@ -41,11 +41,13 @@ class Activity extends \OCA\Files_Sharing\Tests\TestCase { | |||
protected function setUp() { | |||
parent::setUp(); | |||
$this->activity = new \OCA\Files_Sharing\Activity( | |||
$this->getMock('\OC\L10N\Factory'), | |||
$this->getMockBuilder('\OCP\IURLGenerator') | |||
$this->getMockBuilder('OCP\L10N\IFactory') | |||
->disableOriginalConstructor() | |||
->getMock(), | |||
$this->getMockBuilder('\OCP\Activity\IManager') | |||
$this->getMockBuilder('OCP\IURLGenerator') | |||
->disableOriginalConstructor() | |||
->getMock(), | |||
$this->getMockBuilder('OCP\Activity\IManager') | |||
->disableOriginalConstructor() | |||
->getMock() | |||
); |
@@ -67,7 +67,7 @@ $array = array( | |||
"oc_isadmin" => OC_User::isAdminUser(OC_User::getUser()) ? 'true' : 'false', | |||
"oc_webroot" => "\"".OC::$WEBROOT."\"", | |||
"oc_appswebroots" => str_replace('\\/', '/', json_encode($apps_paths)), // Ugly unescape slashes waiting for better solution | |||
"datepickerFormatDate" => json_encode($l->getDateFormat()), | |||
"datepickerFormatDate" => json_encode($l->l('jsdate', null)), | |||
"dayNames" => json_encode( | |||
array( | |||
(string)$l->t('Sunday'), | |||
@@ -133,7 +133,7 @@ $array = array( | |||
(string)$l->t('Dec.') | |||
) | |||
), | |||
"firstDay" => json_encode($l->getFirstWeekDay()) , | |||
"firstDay" => json_encode($l->l('firstday', null)) , | |||
"oc_config" => json_encode( | |||
array( | |||
'session_lifetime' => min(\OCP\Config::getSystemValue('session_lifetime', OC::$server->getIniWrapper()->getNumeric('session.gc_maxlifetime')), OC::$server->getIniWrapper()->getNumeric('session.gc_maxlifetime')), |
@@ -25,16 +25,48 @@ | |||
namespace OC\L10N; | |||
use OCP\IConfig; | |||
use OCP\IRequest; | |||
use OCP\L10N\IFactory; | |||
/** | |||
* A factory that generates language instances | |||
*/ | |||
class Factory implements IFactory { | |||
/** @var string */ | |||
protected $requestLanguage = ''; | |||
/** | |||
* cached instances | |||
* @var array Structure: Lang => App => \OCP\IL10N | |||
*/ | |||
protected $instances = []; | |||
/** | |||
* @var array Structure: App => string[] | |||
*/ | |||
protected $availableLanguages = []; | |||
/** | |||
* @var array Structure: string => callable | |||
*/ | |||
protected $instances = array(); | |||
protected $pluralFunctions = []; | |||
/** @var IConfig */ | |||
protected $config; | |||
/** @var IRequest */ | |||
protected $request; | |||
/** | |||
* @param IConfig $config | |||
* @param IRequest $request | |||
*/ | |||
public function __construct(IConfig $config, IRequest $request) { | |||
$this->config = $config; | |||
$this->request = $request; | |||
} | |||
/** | |||
* Get a language instance | |||
@@ -44,16 +76,269 @@ class Factory implements IFactory { | |||
* @return \OCP\IL10N | |||
*/ | |||
public function get($app, $lang = null) { | |||
$app = \OC_App::cleanAppId($app); | |||
if ($lang !== null) { | |||
$lang = str_replace(array('\0', '/', '\\', '..'), '', (string) $lang); | |||
} | |||
$key = $lang; | |||
if ($key === null) { | |||
if ($key === null || !$this->languageExists($app, $lang)) { | |||
$key = 'null'; | |||
$lang = $this->findLanguage($app); | |||
} | |||
if (!isset($this->instances[$key][$app])) { | |||
$this->instances[$key][$app] = new \OC_L10N($app, $lang); | |||
$this->instances[$key][$app] = new L10N( | |||
$this, $app, $lang, | |||
$this->getL10nFilesForApp($app, $lang) | |||
); | |||
} | |||
return $this->instances[$key][$app]; | |||
} | |||
/** | |||
* Find the best language | |||
* | |||
* @param string|null $app App id or null for core | |||
* @return string language If nothing works it returns 'en' | |||
*/ | |||
public function findLanguage($app = null) { | |||
if ($this->requestLanguage !== '' && $this->languageExists($app, $this->requestLanguage)) { | |||
return $this->requestLanguage; | |||
} | |||
$userId = \OC_User::getUser(); // FIXME not available in non-static? | |||
$userLang = $userId !== false ? $this->config->getUserValue($userId, 'core', 'lang') : null; | |||
if ($userLang) { | |||
$this->requestLanguage = $userLang; | |||
if ($this->languageExists($app, $userLang)) { | |||
return $userLang; | |||
} | |||
} | |||
$defaultLanguage = $this->config->getSystemValue('default_language', false); | |||
if ($defaultLanguage !== false && $this->languageExists($app, $defaultLanguage)) { | |||
return $defaultLanguage; | |||
} | |||
$lang = $this->setLanguageFromRequest($app); | |||
if ($userId !== false && $app === null && !$userLang) { | |||
$this->config->setUserValue($userId, 'core', 'lang', $lang); | |||
} | |||
return $lang; | |||
} | |||
/** | |||
* Find all available languages for an app | |||
* | |||
* @param string|null $app App id or null for core | |||
* @return array an array of available languages | |||
*/ | |||
public function findAvailableLanguages($app = null) { | |||
$key = $app; | |||
if ($key === null) { | |||
$key = 'null'; | |||
} | |||
// also works with null as key | |||
if (!empty($this->availableLanguages[$key])) { | |||
return $this->availableLanguages[$key]; | |||
} | |||
$available = ['en']; //english is always available | |||
$dir = $this->findL10nDir($app); | |||
if (is_dir($dir)) { | |||
$files = scandir($dir); | |||
if ($files !== false) { | |||
foreach ($files as $file) { | |||
if (substr($file, -5) === '.json' && substr($file, 0, 4) !== 'l10n') { | |||
$available[] = substr($file, 0, -5); | |||
} | |||
} | |||
} | |||
} | |||
$this->availableLanguages[$key] = $available; | |||
return $available; | |||
} | |||
/** | |||
* @param string|null $app App id or null for core | |||
* @param string $lang | |||
* @return bool | |||
*/ | |||
public function languageExists($app, $lang) { | |||
if ($lang === 'en') {//english is always available | |||
return true; | |||
} | |||
$languages = $this->findAvailableLanguages($app); | |||
return array_search($lang, $languages) !== false; | |||
} | |||
/** | |||
* @param string|null $app App id or null for core | |||
* @return string | |||
*/ | |||
public function setLanguageFromRequest($app = null) { | |||
$header = $this->request->getHeader('ACCEPT_LANGUAGE'); | |||
if ($header) { | |||
$available = $this->findAvailableLanguages($app); | |||
// E.g. make sure that 'de' is before 'de_DE'. | |||
sort($available); | |||
$preferences = preg_split('/,\s*/', strtolower($header)); | |||
foreach ($preferences as $preference) { | |||
list($preferred_language) = explode(';', $preference); | |||
$preferred_language = str_replace('-', '_', $preferred_language); | |||
foreach ($available as $available_language) { | |||
if ($preferred_language === strtolower($available_language)) { | |||
if ($app === null && !$this->requestLanguage) { | |||
$this->requestLanguage = $available_language; | |||
} | |||
return $available_language; | |||
} | |||
} | |||
// Fallback from de_De to de | |||
foreach ($available as $available_language) { | |||
if (substr($preferred_language, 0, 2) === $available_language) { | |||
if ($app === null && !$this->requestLanguage) { | |||
$this->requestLanguage = $available_language; | |||
} | |||
return $available_language; | |||
} | |||
} | |||
} | |||
} | |||
if (!$this->requestLanguage) { | |||
$this->requestLanguage = 'en'; | |||
} | |||
return 'en'; // Last try: English | |||
} | |||
/** | |||
* Get a list of language files that should be loaded | |||
* | |||
* @param string $app | |||
* @param string $lang | |||
* @return string[] | |||
*/ | |||
// FIXME This method is only public, until OC_L10N does not need it anymore, | |||
// FIXME This is also the reason, why it is not in the public interface | |||
public function getL10nFilesForApp($app, $lang) { | |||
$languageFiles = []; | |||
$i18nDir = $this->findL10nDir($app); | |||
$transFile = strip_tags($i18nDir) . strip_tags($lang) . '.json'; | |||
if ((\OC_Helper::isSubDirectory($transFile, \OC::$SERVERROOT . '/core/l10n/') | |||
|| \OC_Helper::isSubDirectory($transFile, \OC::$SERVERROOT . '/lib/l10n/') | |||
|| \OC_Helper::isSubDirectory($transFile, \OC::$SERVERROOT . '/settings/l10n/') | |||
|| \OC_Helper::isSubDirectory($transFile, \OC_App::getAppPath($app) . '/l10n/') | |||
) | |||
&& file_exists($transFile)) { | |||
// load the translations file | |||
$languageFiles[] = $transFile; | |||
// merge with translations from theme | |||
$theme = $this->config->getSystemValue('theme'); | |||
if (!empty($theme)) { | |||
$transFile = \OC::$SERVERROOT . '/themes/' . $theme . substr($transFile, strlen(\OC::$SERVERROOT)); | |||
if (file_exists($transFile)) { | |||
$languageFiles[] = $transFile; | |||
} | |||
} | |||
} | |||
return $languageFiles; | |||
} | |||
/** | |||
* find the l10n directory | |||
* | |||
* @param string $app App id or empty string for core | |||
* @return string directory | |||
*/ | |||
protected function findL10nDir($app = null) { | |||
if (in_array($app, ['core', 'lib', 'settings'])) { | |||
if (file_exists(\OC::$SERVERROOT . '/' . $app . '/l10n/')) { | |||
return \OC::$SERVERROOT . '/' . $app . '/l10n/'; | |||
} | |||
} else if ($app && \OC_App::getAppPath($app) !== false) { | |||
// Check if the app is in the app folder | |||
return \OC_App::getAppPath($app) . '/l10n/'; | |||
} | |||
return \OC::$SERVERROOT . '/core/l10n/'; | |||
} | |||
/** | |||
* Creates a function from the plural string | |||
* | |||
* Parts of the code is copied from Habari: | |||
* https://github.com/habari/system/blob/master/classes/locale.php | |||
* @param string $string | |||
* @return string | |||
*/ | |||
public function createPluralFunction($string) { | |||
if (isset($this->pluralFunctions[$string])) { | |||
return $this->pluralFunctions[$string]; | |||
} | |||
if (preg_match( '/^\s*nplurals\s*=\s*(\d+)\s*;\s*plural=(.*)$/u', $string, $matches)) { | |||
// sanitize | |||
$nplurals = preg_replace( '/[^0-9]/', '', $matches[1] ); | |||
$plural = preg_replace( '#[^n0-9:\(\)\?\|\&=!<>+*/\%-]#', '', $matches[2] ); | |||
$body = str_replace( | |||
array( 'plural', 'n', '$n$plurals', ), | |||
array( '$plural', '$n', '$nplurals', ), | |||
'nplurals='. $nplurals . '; plural=' . $plural | |||
); | |||
// add parents | |||
// important since PHP's ternary evaluates from left to right | |||
$body .= ';'; | |||
$res = ''; | |||
$p = 0; | |||
for($i = 0; $i < strlen($body); $i++) { | |||
$ch = $body[$i]; | |||
switch ( $ch ) { | |||
case '?': | |||
$res .= ' ? ('; | |||
$p++; | |||
break; | |||
case ':': | |||
$res .= ') : ('; | |||
break; | |||
case ';': | |||
$res .= str_repeat( ')', $p ) . ';'; | |||
$p = 0; | |||
break; | |||
default: | |||
$res .= $ch; | |||
} | |||
} | |||
$body = $res . 'return ($plural>=$nplurals?$nplurals-1:$plural);'; | |||
$function = create_function('$n', $body); | |||
$this->pluralFunctions[$string] = $function; | |||
return $function; | |||
} else { | |||
// default: one plural form for all cases but n==1 (english) | |||
$function = create_function( | |||
'$n', | |||
'$nplurals=2;$plural=($n==1?0:1);return ($plural>=$nplurals?$nplurals-1:$plural);' | |||
); | |||
$this->pluralFunctions[$string] = $function; | |||
return $function; | |||
} | |||
} | |||
} |
@@ -0,0 +1,216 @@ | |||
<?php | |||
/** | |||
* @author Joas Schilling <nickvergessen@owncloud.com> | |||
* | |||
* @copyright Copyright (c) 2016, ownCloud, Inc. | |||
* @license AGPL-3.0 | |||
* | |||
* This code is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License, version 3, | |||
* as published by the Free Software Foundation. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License, version 3, | |||
* along with this program. If not, see <http://www.gnu.org/licenses/> | |||
* | |||
*/ | |||
namespace OC\L10N; | |||
use OCP\IL10N; | |||
use OCP\L10N\IFactory; | |||
use Punic\Calendar; | |||
class L10N implements IL10N { | |||
/** @var IFactory */ | |||
protected $factory; | |||
/** @var string App of this object */ | |||
protected $app; | |||
/** @var string Language of this object */ | |||
protected $lang; | |||
/** @var string Plural forms (string) */ | |||
private $pluralFormString = 'nplurals=2; plural=(n != 1);'; | |||
/** @var string Plural forms (function) */ | |||
private $pluralFormFunction = null; | |||
/** @var string[] */ | |||
private $translations = []; | |||
/** | |||
* @param IFactory $factory | |||
* @param string $app | |||
* @param string $lang | |||
* @param array $files | |||
*/ | |||
public function __construct(IFactory $factory, $app, $lang, array $files) { | |||
$this->factory = $factory; | |||
$this->app = $app; | |||
$this->lang = $lang; | |||
$this->translations = []; | |||
foreach ($files as $languageFile) { | |||
$this->load($languageFile); | |||
} | |||
} | |||
/** | |||
* The code (en, de, ...) of the language that is used for this instance | |||
* | |||
* @return string language | |||
*/ | |||
public function getLanguageCode() { | |||
return $this->lang; | |||
} | |||
/** | |||
* Translating | |||
* @param string $text The text we need a translation for | |||
* @param array $parameters default:array() Parameters for sprintf | |||
* @return string Translation or the same text | |||
* | |||
* Returns the translation. If no translation is found, $text will be | |||
* returned. | |||
*/ | |||
public function t($text, $parameters = array()) { | |||
return (string) new \OC_L10N_String($this, $text, $parameters); | |||
} | |||
/** | |||
* Translating | |||
* @param string $text_singular the string to translate for exactly one object | |||
* @param string $text_plural the string to translate for n objects | |||
* @param integer $count Number of objects | |||
* @param array $parameters default:array() Parameters for sprintf | |||
* @return string Translation or the same text | |||
* | |||
* Returns the translation. If no translation is found, $text will be | |||
* returned. %n will be replaced with the number of objects. | |||
* | |||
* The correct plural is determined by the plural_forms-function | |||
* provided by the po file. | |||
* | |||
*/ | |||
public function n($text_singular, $text_plural, $count, $parameters = array()) { | |||
$identifier = "_${text_singular}_::_${text_plural}_"; | |||
if (isset($this->translations[$identifier])) { | |||
return (string) new \OC_L10N_String($this, $identifier, $parameters, $count); | |||
} else { | |||
if ($count === 1) { | |||
return (string) new \OC_L10N_String($this, $text_singular, $parameters, $count); | |||
} else { | |||
return (string) new \OC_L10N_String($this, $text_plural, $parameters, $count); | |||
} | |||
} | |||
} | |||
/** | |||
* Localization | |||
* @param string $type Type of localization | |||
* @param \DateTime|int|string $data parameters for this localization | |||
* @param array $options | |||
* @return string|int|false | |||
* | |||
* Returns the localized data. | |||
* | |||
* Implemented types: | |||
* - date | |||
* - Creates a date | |||
* - params: timestamp (int/string) | |||
* - datetime | |||
* - Creates date and time | |||
* - params: timestamp (int/string) | |||
* - time | |||
* - Creates a time | |||
* - params: timestamp (int/string) | |||
* - firstday: Returns the first day of the week (0 sunday - 6 saturday) | |||
* - jsdate: Returns the short JS date format | |||
*/ | |||
public function l($type, $data = null, $options = array()) { | |||
// Use the language of the instance | |||
$locale = $this->getLanguageCode(); | |||
if ($locale === 'sr@latin') { | |||
$locale = 'sr_latn'; | |||
} | |||
if ($type === 'firstday') { | |||
return (int) Calendar::getFirstWeekday($locale); | |||
} | |||
if ($type === 'jsdate') { | |||
return (string) Calendar::getDateFormat('short', $locale); | |||
} | |||
$value = new \DateTime(); | |||
if ($data instanceof \DateTime) { | |||
$value = $data; | |||
} else if (is_string($data) && !is_numeric($data)) { | |||
$data = strtotime($data); | |||
$value->setTimestamp($data); | |||
} else if ($data !== null) { | |||
$value->setTimestamp($data); | |||
} | |||
$options = array_merge(array('width' => 'long'), $options); | |||
$width = $options['width']; | |||
switch ($type) { | |||
case 'date': | |||
return (string) Calendar::formatDate($value, $width, $locale); | |||
case 'datetime': | |||
return (string) Calendar::formatDatetime($value, $width, $locale); | |||
case 'time': | |||
return (string) Calendar::formatTime($value, $width, $locale); | |||
default: | |||
return false; | |||
} | |||
} | |||
/** | |||
* Returns an associative array with all translations | |||
* | |||
* Called by \OC_L10N_String | |||
* @return array | |||
*/ | |||
public function getTranslations() { | |||
return $this->translations; | |||
} | |||
/** | |||
* Returnsed function accepts the argument $n | |||
* | |||
* Called by \OC_L10N_String | |||
* @return string the plural form function | |||
*/ | |||
public function getPluralFormFunction() { | |||
if (is_null($this->pluralFormFunction)) { | |||
$this->pluralFormFunction = $this->factory->createPluralFunction($this->pluralFormString); | |||
} | |||
return $this->pluralFormFunction; | |||
} | |||
/** | |||
* @param $translationFile | |||
* @return bool | |||
*/ | |||
protected function load($translationFile) { | |||
$json = json_decode(file_get_contents($translationFile), true); | |||
if (!is_array($json)) { | |||
$jsonError = json_last_error(); | |||
\OC::$server->getLogger()->warning("Failed to load $translationFile - json error code: $jsonError", ['app' => 'l10n']); | |||
return false; | |||
} | |||
if (!empty($json['pluralForm'])) { | |||
$this->pluralFormString = $json['pluralForm']; | |||
} | |||
$this->translations = array_merge($this->translations, $json['translations']); | |||
return true; | |||
} | |||
} |
@@ -26,28 +26,23 @@ | |||
*/ | |||
class OC_L10N_String implements JsonSerializable { | |||
/** | |||
* @var OC_L10N | |||
*/ | |||
/** @var \OC_L10N|\OC\L10N\L10N */ | |||
protected $l10n; | |||
/** | |||
* @var string | |||
*/ | |||
/** @var string */ | |||
protected $text; | |||
/** | |||
* @var array | |||
*/ | |||
/** @var array */ | |||
protected $parameters; | |||
/** | |||
* @var integer | |||
*/ | |||
/** @var integer */ | |||
protected $count; | |||
/** | |||
* @param OC_L10N $l10n | |||
* @param \OC_L10N|\OC\L10N\L10N $l10n | |||
* @param string|string[] $text | |||
* @param array $parameters | |||
* @param int $count | |||
*/ | |||
public function __construct($l10n, $text, $parameters, $count = 1) { | |||
$this->l10n = $l10n; | |||
@@ -80,5 +75,4 @@ class OC_L10N_String implements JsonSerializable { | |||
public function jsonSerialize() { | |||
return $this->__toString(); | |||
} | |||
} |
@@ -37,6 +37,7 @@ | |||
/** | |||
* This class is for i18n and l10n | |||
* @deprecated 9.0.0 Use \OC::$server->getL10NFactory()->get() instead | |||
*/ | |||
class OC_L10N implements \OCP\IL10N { | |||
/** | |||
@@ -82,56 +83,29 @@ class OC_L10N implements \OCP\IL10N { | |||
* | |||
* If language is not set, the constructor tries to find the right | |||
* language. | |||
* @deprecated 9.0.0 Use \OC::$server->getL10NFactory()->get() instead | |||
*/ | |||
public function __construct($app, $lang = null) { | |||
$app = \OC_App::cleanAppId($app); | |||
$this->app = $app; | |||
$this->lang = $lang; | |||
} | |||
/** | |||
* @return string | |||
*/ | |||
public static function setLanguageFromRequest() { | |||
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { | |||
$available = self::findAvailableLanguages(); | |||
// E.g. make sure that 'de' is before 'de_DE'. | |||
sort($available); | |||
$preferences = preg_split('/,\s*/', strtolower($_SERVER['HTTP_ACCEPT_LANGUAGE'])); | |||
foreach ($preferences as $preference) { | |||
list($preferred_language) = explode(';', $preference); | |||
$preferred_language = str_replace('-', '_', $preferred_language); | |||
foreach ($available as $available_language) { | |||
if ($preferred_language === strtolower($available_language)) { | |||
if (!self::$language) { | |||
self::$language = $available_language; | |||
} | |||
return $available_language; | |||
} | |||
} | |||
foreach ($available as $available_language) { | |||
if (substr($preferred_language, 0, 2) === $available_language) { | |||
if (!self::$language) { | |||
self::$language = $available_language; | |||
} | |||
return $available_language; | |||
} | |||
} | |||
} | |||
if ($lang !== null) { | |||
$lang = str_replace(array('\0', '/', '\\', '..'), '', $lang); | |||
} | |||
self::$language = 'en'; | |||
// Last try: English | |||
return 'en'; | |||
// Find the right language | |||
if ($app !== 'test' && !\OC::$server->getL10NFactory()->languageExists($app, $lang)) { | |||
$lang = \OC::$server->getL10NFactory()->findLanguage($app); | |||
} | |||
$this->lang = $lang; | |||
} | |||
/** | |||
* @param $transFile | |||
* @param bool $mergeTranslations | |||
* @return bool | |||
*/ | |||
public function load($transFile, $mergeTranslations = false) { | |||
public function load($transFile) { | |||
$this->app = true; | |||
$json = json_decode(file_get_contents($transFile), true); | |||
@@ -144,11 +118,7 @@ class OC_L10N implements \OCP\IL10N { | |||
$this->pluralFormString = $json['pluralForm']; | |||
$translations = $json['translations']; | |||
if ($mergeTranslations) { | |||
$this->translations = array_merge($this->translations, $translations); | |||
} else { | |||
$this->translations = $translations; | |||
} | |||
$this->translations = array_merge($this->translations, $translations); | |||
return true; | |||
} | |||
@@ -157,101 +127,17 @@ class OC_L10N implements \OCP\IL10N { | |||
if ($this->app === true) { | |||
return; | |||
} | |||
$app = OC_App::cleanAppId($this->app); | |||
$lang = str_replace(array('\0', '/', '\\', '..'), '', $this->lang); | |||
$app = $this->app; | |||
$lang = $this->lang; | |||
$this->app = true; | |||
// Find the right language | |||
if(is_null($lang) || $lang == '') { | |||
$lang = self::findLanguage($app); | |||
} | |||
// Use cache if possible | |||
if(array_key_exists($app.'::'.$lang, self::$cache)) { | |||
$this->translations = self::$cache[$app.'::'.$lang]['t']; | |||
} else{ | |||
$i18nDir = self::findI18nDir($app); | |||
$transFile = strip_tags($i18nDir).strip_tags($lang).'.json'; | |||
// Texts are in $i18ndir | |||
// (Just no need to define date/time format etc. twice) | |||
if((OC_Helper::isSubDirectory($transFile, OC::$SERVERROOT.'/core/l10n/') | |||
|| OC_Helper::isSubDirectory($transFile, OC::$SERVERROOT.'/lib/l10n/') | |||
|| OC_Helper::isSubDirectory($transFile, OC::$SERVERROOT.'/settings') | |||
|| OC_Helper::isSubDirectory($transFile, OC_App::getAppPath($app).'/l10n/') | |||
) | |||
&& file_exists($transFile)) { | |||
// load the translations file | |||
if($this->load($transFile)) { | |||
//merge with translations from theme | |||
$theme = \OC::$server->getConfig()->getSystemValue('theme'); | |||
if (!empty($theme)) { | |||
$transFile = OC::$SERVERROOT.'/themes/'.$theme.substr($transFile, strlen(OC::$SERVERROOT)); | |||
if (file_exists($transFile)) { | |||
$this->load($transFile, true); | |||
} | |||
} | |||
} | |||
} | |||
/** @var \OC\L10N\Factory $factory */ | |||
$factory = \OC::$server->getL10NFactory(); | |||
$languageFiles = $factory->getL10nFilesForApp($app, $lang); | |||
self::$cache[$app.'::'.$lang]['t'] = $this->translations; | |||
} | |||
} | |||
/** | |||
* Creates a function that The constructor | |||
* | |||
* If language is not set, the constructor tries to find the right | |||
* language. | |||
* | |||
* Parts of the code is copied from Habari: | |||
* https://github.com/habari/system/blob/master/classes/locale.php | |||
* @param string $string | |||
* @return string | |||
*/ | |||
protected function createPluralFormFunction($string){ | |||
if(preg_match( '/^\s*nplurals\s*=\s*(\d+)\s*;\s*plural=(.*)$/u', $string, $matches)) { | |||
// sanitize | |||
$nplurals = preg_replace( '/[^0-9]/', '', $matches[1] ); | |||
$plural = preg_replace( '#[^n0-9:\(\)\?\|\&=!<>+*/\%-]#', '', $matches[2] ); | |||
$body = str_replace( | |||
array( 'plural', 'n', '$n$plurals', ), | |||
array( '$plural', '$n', '$nplurals', ), | |||
'nplurals='. $nplurals . '; plural=' . $plural | |||
); | |||
// add parents | |||
// important since PHP's ternary evaluates from left to right | |||
$body .= ';'; | |||
$res = ''; | |||
$p = 0; | |||
for($i = 0; $i < strlen($body); $i++) { | |||
$ch = $body[$i]; | |||
switch ( $ch ) { | |||
case '?': | |||
$res .= ' ? ('; | |||
$p++; | |||
break; | |||
case ':': | |||
$res .= ') : ('; | |||
break; | |||
case ';': | |||
$res .= str_repeat( ')', $p ) . ';'; | |||
$p = 0; | |||
break; | |||
default: | |||
$res .= $ch; | |||
} | |||
} | |||
$body = $res . 'return ($plural>=$nplurals?$nplurals-1:$plural);'; | |||
return create_function('$n', $body); | |||
} | |||
else { | |||
// default: one plural form for all cases but n==1 (english) | |||
return create_function( | |||
'$n', | |||
'$nplurals=2;$plural=($n==1?0:1);return ($plural>=$nplurals?$nplurals-1:$plural);' | |||
); | |||
$this->translations = []; | |||
foreach ($languageFiles as $languageFile) { | |||
$this->load($languageFile); | |||
} | |||
} | |||
@@ -316,8 +202,8 @@ class OC_L10N implements \OCP\IL10N { | |||
*/ | |||
public function getPluralFormFunction() { | |||
$this->init(); | |||
if(is_null($this->pluralFormFunction)) { | |||
$this->pluralFormFunction = $this->createPluralFormFunction($this->pluralFormString); | |||
if (is_null($this->pluralFormFunction)) { | |||
$this->pluralFormFunction = \OC::$server->getL10NFactory()->createPluralFunction($this->pluralFormString); | |||
} | |||
return $this->pluralFormFunction; | |||
} | |||
@@ -341,6 +227,8 @@ class OC_L10N implements \OCP\IL10N { | |||
* - time | |||
* - Creates a time | |||
* - params: timestamp (int/string) | |||
* - firstday: Returns the first day of the week (0 sunday - 6 saturday) | |||
* - jsdate: Returns the short JS date format | |||
*/ | |||
public function l($type, $data, $options = array()) { | |||
if ($type === 'firstday') { | |||
@@ -361,12 +249,8 @@ class OC_L10N implements \OCP\IL10N { | |||
$value->setTimestamp($data); | |||
} | |||
// Use the language of the instance, before falling back to the current user's language | |||
$locale = $this->lang; | |||
if ($locale === null) { | |||
$locale = self::findLanguage(); | |||
} | |||
$locale = $this->transformToCLDRLocale($locale); | |||
// Use the language of the instance | |||
$locale = $this->transformToCLDRLocale($this->getLanguageCode()); | |||
$options = array_merge(array('width' => 'long'), $options); | |||
$width = $options['width']; | |||
@@ -388,132 +272,77 @@ class OC_L10N implements \OCP\IL10N { | |||
* @return string language | |||
*/ | |||
public function getLanguageCode() { | |||
return $this->lang ? $this->lang : self::findLanguage(); | |||
return $this->lang; | |||
} | |||
/** | |||
* find the best language | |||
* @param string $app | |||
* @return string language | |||
* | |||
* If nothing works it returns 'en' | |||
* @return string | |||
* @throws \Punic\Exception\ValueNotInList | |||
* @deprecated 9.0.0 Use $this->l('jsdate', null) instead | |||
*/ | |||
public static function findLanguage($app = null) { | |||
if (self::$language != '' && self::languageExists($app, self::$language)) { | |||
return self::$language; | |||
} | |||
$config = \OC::$server->getConfig(); | |||
$userId = \OC_User::getUser(); | |||
if($userId && $config->getUserValue($userId, 'core', 'lang')) { | |||
$lang = $config->getUserValue($userId, 'core', 'lang'); | |||
self::$language = $lang; | |||
if(self::languageExists($app, $lang)) { | |||
return $lang; | |||
} | |||
} | |||
$default_language = $config->getSystemValue('default_language', false); | |||
if($default_language !== false) { | |||
return $default_language; | |||
} | |||
$lang = self::setLanguageFromRequest(); | |||
if($userId && !$config->getUserValue($userId, 'core', 'lang')) { | |||
$config->setUserValue($userId, 'core', 'lang', $lang); | |||
} | |||
return $lang; | |||
public function getDateFormat() { | |||
$locale = $this->transformToCLDRLocale($this->getLanguageCode()); | |||
return Punic\Calendar::getDateFormat('short', $locale); | |||
} | |||
/** | |||
* find the l10n directory | |||
* @param string $app App that needs to be translated | |||
* @return string directory | |||
* @return int | |||
* @deprecated 9.0.0 Use $this->l('firstday', null) instead | |||
*/ | |||
protected static function findI18nDir($app) { | |||
// find the i18n dir | |||
$i18nDir = OC::$SERVERROOT.'/core/l10n/'; | |||
if($app != '') { | |||
// Check if the app is in the app folder | |||
if(file_exists(OC_App::getAppPath($app).'/l10n/')) { | |||
$i18nDir = OC_App::getAppPath($app).'/l10n/'; | |||
} | |||
else{ | |||
$i18nDir = OC::$SERVERROOT.'/'.$app.'/l10n/'; | |||
} | |||
} | |||
return $i18nDir; | |||
public function getFirstWeekDay() { | |||
$locale = $this->transformToCLDRLocale($this->getLanguageCode()); | |||
return Punic\Calendar::getFirstWeekday($locale); | |||
} | |||
/** | |||
* find all available languages for an app | |||
* @param string $app App that needs to be translated | |||
* @return array an array of available languages | |||
* @param string $locale | |||
* @return string | |||
*/ | |||
public static function findAvailableLanguages($app=null) { | |||
// also works with null as key | |||
if(isset(self::$availableLanguages[$app]) && !empty(self::$availableLanguages[$app])) { | |||
return self::$availableLanguages[$app]; | |||
} | |||
$available=array('en');//english is always available | |||
$dir = self::findI18nDir($app); | |||
if(is_dir($dir)) { | |||
$files=scandir($dir); | |||
foreach($files as $file) { | |||
if(substr($file, -5, 5) === '.json' && substr($file, 0, 4) !== 'l10n') { | |||
$i = substr($file, 0, -5); | |||
$available[] = $i; | |||
} | |||
} | |||
private function transformToCLDRLocale($locale) { | |||
if ($locale === 'sr@latin') { | |||
return 'sr_latn'; | |||
} | |||
self::$availableLanguages[$app] = $available; | |||
return $available; | |||
return $locale; | |||
} | |||
/** | |||
* find the best language | |||
* @param string $app | |||
* @param string $lang | |||
* @return bool | |||
* @return string language | |||
* | |||
* If nothing works it returns 'en' | |||
* @deprecated 9.0.0 Use \OC::$server->getL10NFactory()->findLanguage() instead | |||
*/ | |||
public static function languageExists($app, $lang) { | |||
if ($lang === 'en') {//english is always available | |||
return true; | |||
} | |||
$dir = self::findI18nDir($app); | |||
if(is_dir($dir)) { | |||
return file_exists($dir.'/'.$lang.'.json'); | |||
} | |||
return false; | |||
public static function findLanguage($app = null) { | |||
return \OC::$server->getL10NFactory()->findLanguage($app); | |||
} | |||
/** | |||
* @return string | |||
* @throws \Punic\Exception\ValueNotInList | |||
* @deprecated 9.0.0 Use \OC::$server->getL10NFactory()->setLanguageFromRequest() instead | |||
*/ | |||
public function getDateFormat() { | |||
$locale = $this->getLanguageCode(); | |||
$locale = $this->transformToCLDRLocale($locale); | |||
return Punic\Calendar::getDateFormat('short', $locale); | |||
public static function setLanguageFromRequest() { | |||
return \OC::$server->getL10NFactory()->setLanguageFromRequest(); | |||
} | |||
/** | |||
* @return int | |||
* find all available languages for an app | |||
* @param string $app App that needs to be translated | |||
* @return array an array of available languages | |||
* @deprecated 9.0.0 Use \OC::$server->getL10NFactory()->findAvailableLanguages() instead | |||
*/ | |||
public function getFirstWeekDay() { | |||
$locale = $this->getLanguageCode(); | |||
$locale = $this->transformToCLDRLocale($locale); | |||
return Punic\Calendar::getFirstWeekday($locale); | |||
public static function findAvailableLanguages($app=null) { | |||
return \OC::$server->getL10NFactory()->findAvailableLanguages($app); | |||
} | |||
private function transformToCLDRLocale($locale) { | |||
if ($locale === 'sr@latin') { | |||
return 'sr_latn'; | |||
} | |||
return $locale; | |||
/** | |||
* @param string $app | |||
* @param string $lang | |||
* @return bool | |||
* @deprecated 9.0.0 Use \OC::$server->getL10NFactory()->languageExists() instead | |||
*/ | |||
public static function languageExists($app, $lang) { | |||
return \OC::$server->getL10NFactory()->languageExists($app, $lang); | |||
} | |||
} |
@@ -262,8 +262,11 @@ class Server extends ServerContainer implements IServerContainer { | |||
$this->registerService('AppConfig', function (Server $c) { | |||
return new \OC\AppConfig($c->getDatabaseConnection()); | |||
}); | |||
$this->registerService('L10NFactory', function ($c) { | |||
return new \OC\L10N\Factory(); | |||
$this->registerService('L10NFactory', function (Server $c) { | |||
return new \OC\L10N\Factory( | |||
$c->getConfig(), | |||
$c->getRequest() | |||
); | |||
}); | |||
$this->registerService('URLGenerator', function (Server $c) { | |||
$config = $c->getConfig(); |
@@ -33,4 +33,47 @@ interface IFactory { | |||
* @since 8.2.0 | |||
*/ | |||
public function get($app, $lang = null); | |||
/** | |||
* Find the best language | |||
* | |||
* @param string|null $app App id or null for core | |||
* @return string language If nothing works it returns 'en' | |||
* @since 9.0.0 | |||
*/ | |||
public function findLanguage($app = null); | |||
/** | |||
* Find all available languages for an app | |||
* | |||
* @param string|null $app App id or null for core | |||
* @return string[] an array of available languages | |||
* @since 9.0.0 | |||
*/ | |||
public function findAvailableLanguages($app = null); | |||
/** | |||
* @param string|null $app App id or null for core | |||
* @param string $lang | |||
* @return bool | |||
* @since 9.0.0 | |||
*/ | |||
public function languageExists($app, $lang); | |||
/** | |||
* @param string|null $app App id or null for core | |||
* @return string | |||
* @since 9.0.0 | |||
*/ | |||
public function setLanguageFromRequest($app = null); | |||
/** | |||
* Creates a function from the plural string | |||
* | |||
* @param string $string | |||
* @return string Unique function name | |||
* @since 9.0.0 | |||
*/ | |||
public function createPluralFunction($string); | |||
} |
@@ -46,7 +46,7 @@ try { | |||
OC_App::loadApps(); | |||
// force language as given in the http request | |||
\OC_L10N::setLanguageFromRequest(); | |||
\OC::$server->getL10NFactory()->setLanguageFromRequest(); | |||
OC::$server->getRouter()->match('/ocs'.\OC::$server->getRequest()->getRawPathInfo()); | |||
} catch (ResourceNotFoundException $e) { |
@@ -109,7 +109,7 @@ try { | |||
} | |||
// force language as given in the http request | |||
\OC_L10N::setLanguageFromRequest(); | |||
\OC::$server->getL10NFactory()->setLanguageFromRequest(); | |||
$file=ltrim($file, '/'); | |||
@@ -31,7 +31,7 @@ OCP\JSON::callCheck(); | |||
// Get data | |||
if( isset( $_POST['lang'] ) ) { | |||
$languageCodes=OC_L10N::findAvailableLanguages(); | |||
$languageCodes = \OC::$server->getL10NFactory()->findAvailableLanguages(); | |||
$lang = (string)$_POST['lang']; | |||
if(array_search($lang, $languageCodes) or $lang === 'en') { | |||
\OC::$server->getConfig()->setUserValue( OC_User::getUser(), 'core', 'lang', $lang ); |
@@ -63,7 +63,7 @@ $user = OC::$server->getUserManager()->get(OC_User::getUser()); | |||
$email = $user->getEMailAddress(); | |||
$userLang=$config->getUserValue( OC_User::getUser(), 'core', 'lang', OC_L10N::findLanguage() ); | |||
$languageCodes=OC_L10N::findAvailableLanguages(); | |||
$languageCodes = \OC::$server->getL10NFactory()->findAvailableLanguages(); | |||
// array of common languages | |||
$commonLangCodes = array( |
@@ -0,0 +1,310 @@ | |||
<?php | |||
/** | |||
* Copyright (c) 2016 Joas Schilling <nickvergessen@owncloud.com> | |||
* This file is licensed under the Affero General Public License version 3 or | |||
* later. | |||
* See the COPYING-README file. | |||
*/ | |||
namespace Test\L10N; | |||
use OC\L10N\Factory; | |||
use Test\TestCase; | |||
/** | |||
* Class FactoryTest | |||
* | |||
* @package Test\L10N | |||
* @group DB | |||
*/ | |||
class FactoryTest extends TestCase { | |||
/** @var \OCP\IConfig|\PHPUnit_Framework_MockObject_MockObject */ | |||
protected $config; | |||
/** @var \OCP\IRequest|\PHPUnit_Framework_MockObject_MockObject */ | |||
protected $request; | |||
public function setUp() { | |||
parent::setUp(); | |||
/** @var \OCP\IConfig $request */ | |||
$this->config = $this->getMockBuilder('OCP\IConfig') | |||
->disableOriginalConstructor() | |||
->getMock(); | |||
/** @var \OCP\IRequest $request */ | |||
$this->request = $this->getMockBuilder('OCP\IRequest') | |||
->disableOriginalConstructor() | |||
->getMock(); | |||
} | |||
/** | |||
* @param array $methods | |||
* @return Factory|\PHPUnit_Framework_MockObject_MockObject | |||
*/ | |||
protected function getFactory(array $methods = []) { | |||
if (!empty($methods)) { | |||
return $this->getMockBuilder('OC\L10N\Factory') | |||
->setConstructorArgs([ | |||
$this->config, | |||
$this->request, | |||
]) | |||
->setMethods($methods) | |||
->getMock(); | |||
} else { | |||
return new Factory($this->config, $this->request); | |||
} | |||
} | |||
public function dataFindLanguage() { | |||
return [ | |||
[null, false, 1, 'de', true, null, null, null, null, null, 'de'], | |||
[null, 'test', 2, 'de', false, 'ru', true, null, null, null, 'ru'], | |||
[null, 'test', 1, '', null, 'ru', true, null, null, null, 'ru'], | |||
[null, 'test', 3, 'de', false, 'ru', false, 'cz', true, null, 'cz'], | |||
[null, 'test', 2, '', null, 'ru', false, 'cz', true, null, 'cz'], | |||
[null, 'test', 1, '', null, '', null, 'cz', true, null, 'cz'], | |||
[null, 'test', 3, 'de', false, 'ru', false, 'cz', false, 'ar', 'ar'], | |||
[null, 'test', 2, '', null, 'ru', false, 'cz', false, 'ar', 'ar'], | |||
[null, 'test', 1, '', null, '', null, 'cz', false, 'ar', 'ar'], | |||
[null, 'test', 0, '', null, '', null, false, null, 'ar', 'ar'], | |||
]; | |||
} | |||
/** | |||
* @dataProvider dataFindLanguage | |||
* | |||
* @param string|null $app | |||
* @param string|null $user | |||
* @param int $existsCalls | |||
* @param string $storedRequestLang | |||
* @param bool $srlExists | |||
* @param string|null $userLang | |||
* @param bool $ulExists | |||
* @param string|false $defaultLang | |||
* @param bool $dlExists | |||
* @param string|null $requestLang | |||
* @param string $expected | |||
*/ | |||
public function testFindLanguage($app, $user, $existsCalls, $storedRequestLang, $srlExists, $userLang, $ulExists, $defaultLang, $dlExists, $requestLang, $expected) { | |||
$factory = $this->getFactory([ | |||
'languageExists', | |||
'setLanguageFromRequest', | |||
]); | |||
$session = $this->getMockBuilder('OCP\ISession') | |||
->disableOriginalConstructor() | |||
->getMock(); | |||
$session->expects($this->any()) | |||
->method('get') | |||
->with('user_id') | |||
->willReturn($user); | |||
$userSession = $this->getMockBuilder('OC\User\Session') | |||
->disableOriginalConstructor() | |||
->getMock(); | |||
$userSession->expects($this->any()) | |||
->method('getSession') | |||
->willReturn($session); | |||
$this->invokePrivate($factory, 'requestLanguage', [$storedRequestLang]); | |||
$factory->expects($this->exactly($existsCalls)) | |||
->method('languageExists') | |||
->willReturnMap([ | |||
[$app, $storedRequestLang, $srlExists], | |||
[$app, $userLang, $ulExists], | |||
[$app, $defaultLang, $dlExists], | |||
]); | |||
$factory->expects($requestLang !== null ? $this->once() : $this->never()) | |||
->method('setLanguageFromRequest') | |||
->willReturn($requestLang); | |||
$this->config->expects($userLang !== null ? $this->any() : $this->never()) | |||
->method('getUserValue') | |||
->with($this->anything(), 'core', 'lang') | |||
->willReturn($userLang); | |||
$this->config->expects($defaultLang !== null ? $this->once() : $this->never()) | |||
->method('getSystemValue') | |||
->with('default_language', false) | |||
->willReturn($defaultLang); | |||
$this->overwriteService('UserSession', $userSession); | |||
$this->assertSame($expected, $factory->findLanguage($app)); | |||
$this->restoreService('UserSession'); | |||
} | |||
public function dataFindAvailableLanguages() { | |||
return [ | |||
[null], | |||
['files'], | |||
]; | |||
} | |||
/** | |||
* @dataProvider dataFindAvailableLanguages | |||
* | |||
* @param string|null $app | |||
*/ | |||
public function testFindAvailableLanguages($app) { | |||
$factory = $this->getFactory(['findL10nDir']); | |||
$factory->expects($this->once()) | |||
->method('findL10nDir') | |||
->with($app) | |||
->willReturn(\OC::$SERVERROOT . '/tests/data/l10n/'); | |||
$this->assertEquals(['cs', 'de', 'en', 'ru'], $factory->findAvailableLanguages($app), '', 0.0, 10, true); | |||
} | |||
public function dataLanguageExists() { | |||
return [ | |||
[null, 'en', [], true], | |||
[null, 'de', [], false], | |||
[null, 'de', ['ru'], false], | |||
[null, 'de', ['ru', 'de'], true], | |||
['files', 'en', [], true], | |||
['files', 'de', [], false], | |||
['files', 'de', ['ru'], false], | |||
['files', 'de', ['de', 'ru'], true], | |||
]; | |||
} | |||
/** | |||
* @dataProvider dataLanguageExists | |||
* | |||
* @param string|null $app | |||
* @param string $lang | |||
* @param string[] $availableLanguages | |||
* @param string $expected | |||
*/ | |||
public function testLanguageExists($app, $lang, array $availableLanguages, $expected) { | |||
$factory = $this->getFactory(['findAvailableLanguages']); | |||
$factory->expects(($lang === 'en') ? $this->never() : $this->once()) | |||
->method('findAvailableLanguages') | |||
->with($app) | |||
->willReturn($availableLanguages); | |||
$this->assertSame($expected, $factory->languageExists($app, $lang)); | |||
} | |||
public function dataSetLanguageFromRequest() { | |||
return [ | |||
// Language is available | |||
[null, 'de', null, ['de'], 'de', 'de'], | |||
[null, 'de,en', null, ['de'], 'de', 'de'], | |||
[null, 'de-DE,en-US;q=0.8,en;q=0.6', null, ['de'], 'de', 'de'], | |||
// Language is not available | |||
[null, 'de', null, ['ru'], 'en', 'en'], | |||
[null, 'de,en', null, ['ru', 'en'], 'en', 'en'], | |||
[null, 'de-DE,en-US;q=0.8,en;q=0.6', null, ['ru', 'en'], 'en', 'en'], | |||
// Language is available, but request language is set | |||
[null, 'de', 'ru', ['de'], 'de', 'ru'], | |||
[null, 'de,en', 'ru', ['de'], 'de', 'ru'], | |||
[null, 'de-DE,en-US;q=0.8,en;q=0.6', 'ru', ['de'], 'de', 'ru'], | |||
]; | |||
} | |||
/** | |||
* @dataProvider dataSetLanguageFromRequest | |||
* | |||
* @param string|null $app | |||
* @param string $header | |||
* @param string|null $requestLanguage | |||
* @param string[] $availableLanguages | |||
* @param string $expected | |||
* @param string $expectedLang | |||
*/ | |||
public function testSetLanguageFromRequest($app, $header, $requestLanguage, array $availableLanguages, $expected, $expectedLang) { | |||
$factory = $this->getFactory(['findAvailableLanguages']); | |||
$factory->expects($this->once()) | |||
->method('findAvailableLanguages') | |||
->with($app) | |||
->willReturn($availableLanguages); | |||
$this->request->expects($this->once()) | |||
->method('getHeader') | |||
->with('ACCEPT_LANGUAGE') | |||
->willReturn($header); | |||
if ($requestLanguage !== null) { | |||
$this->invokePrivate($factory, 'requestLanguage', [$requestLanguage]); | |||
} | |||
$this->assertSame($expected, $factory->setLanguageFromRequest($app), 'Asserting returned language'); | |||
$this->assertSame($expectedLang, $this->invokePrivate($factory, 'requestLanguage'), 'Asserting stored language'); | |||
} | |||
public function dataGetL10nFilesForApp() { | |||
return [ | |||
[null, 'de', [\OC::$SERVERROOT . '/core/l10n/de.json']], | |||
['core', 'ru', [\OC::$SERVERROOT . '/core/l10n/ru.json']], | |||
['lib', 'ru', [\OC::$SERVERROOT . '/lib/l10n/ru.json']], | |||
['settings', 'de', [\OC::$SERVERROOT . '/settings/l10n/de.json']], | |||
['files', 'de', [\OC::$SERVERROOT . '/apps/files/l10n/de.json']], | |||
['files', '_lang_never_exists_', []], | |||
['_app_never_exists_', 'de', [\OC::$SERVERROOT . '/core/l10n/de.json']], | |||
]; | |||
} | |||
/** | |||
* @dataProvider dataGetL10nFilesForApp | |||
* | |||
* @param string|null $app | |||
* @param string $expected | |||
*/ | |||
public function testGetL10nFilesForApp($app, $lang, $expected) { | |||
$factory = $this->getFactory(); | |||
$this->assertSame($expected, $this->invokePrivate($factory, 'getL10nFilesForApp', [$app, $lang])); | |||
} | |||
public function dataFindL10NDir() { | |||
return [ | |||
[null, \OC::$SERVERROOT . '/core/l10n/'], | |||
['core', \OC::$SERVERROOT . '/core/l10n/'], | |||
['lib', \OC::$SERVERROOT . '/lib/l10n/'], | |||
['settings', \OC::$SERVERROOT . '/settings/l10n/'], | |||
['files', \OC::$SERVERROOT . '/apps/files/l10n/'], | |||
['_app_never_exists_', \OC::$SERVERROOT . '/core/l10n/'], | |||
]; | |||
} | |||
/** | |||
* @dataProvider dataFindL10NDir | |||
* | |||
* @param string|null $app | |||
* @param string $expected | |||
*/ | |||
public function testFindL10NDir($app, $expected) { | |||
$factory = $this->getFactory(); | |||
$this->assertSame($expected, $this->invokePrivate($factory, 'findL10nDir', [$app])); | |||
} | |||
public function dataCreatePluralFunction() { | |||
return [ | |||
['nplurals=2; plural=(n != 1);', 0, 1], | |||
['nplurals=2; plural=(n != 1);', 1, 0], | |||
['nplurals=2; plural=(n != 1);', 2, 1], | |||
['nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;', 0, 2], | |||
['nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;', 1, 0], | |||
['nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;', 2, 1], | |||
['nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;', 3, 1], | |||
['nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;', 4, 1], | |||
['nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;', 5, 2], | |||
]; | |||
} | |||
/** | |||
* @dataProvider dataCreatePluralFunction | |||
* | |||
* @param string $function | |||
* @param int $count | |||
* @param int $expected | |||
*/ | |||
public function testCreatePluralFunction($function, $count, $expected) { | |||
$factory = $this->getFactory(); | |||
$fn = $factory->createPluralFunction($function); | |||
$this->assertEquals($expected, $fn($count)); | |||
} | |||
} |
@@ -6,11 +6,21 @@ | |||
* See the COPYING-README file. | |||
*/ | |||
class Test_L10n extends \Test\TestCase { | |||
namespace Test\L10N; | |||
use OC_L10N; | |||
use DateTime; | |||
/** | |||
* Class Test_L10n | |||
* @group DB | |||
*/ | |||
class L10nLegacyTest extends \Test\TestCase { | |||
public function testGermanPluralTranslations() { | |||
$l = new OC_L10N('test'); | |||
$transFile = OC::$SERVERROOT.'/tests/data/l10n/de.json'; | |||
$transFile = \OC::$SERVERROOT.'/tests/data/l10n/de.json'; | |||
$l->load($transFile); | |||
$this->assertEquals('1 Datei', (string)$l->n('%n file', '%n files', 1)); | |||
@@ -19,7 +29,7 @@ class Test_L10n extends \Test\TestCase { | |||
public function testRussianPluralTranslations() { | |||
$l = new OC_L10N('test'); | |||
$transFile = OC::$SERVERROOT.'/tests/data/l10n/ru.json'; | |||
$transFile = \OC::$SERVERROOT.'/tests/data/l10n/ru.json'; | |||
$l->load($transFile); | |||
$this->assertEquals('1 файл', (string)$l->n('%n file', '%n files', 1)); | |||
@@ -44,7 +54,7 @@ class Test_L10n extends \Test\TestCase { | |||
public function testCzechPluralTranslations() { | |||
$l = new OC_L10N('test'); | |||
$transFile = OC::$SERVERROOT.'/tests/data/l10n/cs.json'; | |||
$transFile = \OC::$SERVERROOT.'/tests/data/l10n/cs.json'; | |||
$l->load($transFile); | |||
$this->assertEquals('1 okno', (string)$l->n('%n window', '%n windows', 1)); | |||
@@ -113,51 +123,8 @@ class Test_L10n extends \Test\TestCase { | |||
$this->assertSame($expected, $l->l('firstday', 'firstday')); | |||
} | |||
/** | |||
* @dataProvider findLanguageData | |||
*/ | |||
public function testFindLanguage($default, $preference, $expected) { | |||
OC_User::setUserId(null); | |||
$config = \OC::$server->getConfig(); | |||
if (is_null($default)) { | |||
$config->deleteSystemValue('default_language'); | |||
} else { | |||
$config->setSystemValue('default_language', $default); | |||
} | |||
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = $preference; | |||
$reflection = new \ReflectionClass('OC_L10N'); | |||
$prop = $reflection->getProperty('language'); | |||
$prop->setAccessible(1); | |||
$prop->setValue(''); | |||
$prop->setAccessible(0); | |||
$this->assertSame($expected, OC_L10N::findLanguage()); | |||
} | |||
public function findLanguageData() { | |||
return array( | |||
// Exact match | |||
array(null, 'de-DE,en;q=0.5', 'de_DE'), | |||
array(null, 'de-DE,en-US;q=0.8,en;q=0.6', 'de_DE'), | |||
// Best match | |||
array(null, 'de-US,en;q=0.5', 'de'), | |||
array(null, 'de-US,en-US;q=0.8,en;q=0.6', 'de'), | |||
// The default_language config setting overrides browser preferences. | |||
array('es_AR', 'de-DE,en;q=0.5', 'es_AR'), | |||
array('es_AR', 'de-DE,en-US;q=0.8,en;q=0.6', 'es_AR'), | |||
// Worst case default to english | |||
array(null, '', 'en'), | |||
array(null, null, 'en'), | |||
); | |||
} | |||
public function testFactoryGetLanguageCode() { | |||
$factory = new \OC\L10N\Factory(); | |||
$factory = new \OC\L10N\Factory($this->getMock('OCP\IConfig'), $this->getMock('OCP\IRequest')); | |||
$l = $factory->get('lib', 'de'); | |||
$this->assertEquals('de', $l->getLanguageCode()); | |||
} |
@@ -0,0 +1,162 @@ | |||
<?php | |||
/** | |||
* Copyright (c) 2016 Joas Schilling <nickvergessen@owncloud.com> | |||
* This file is licensed under the Affero General Public License version 3 or | |||
* later. | |||
* See the COPYING-README file. | |||
*/ | |||
namespace Test\L10N; | |||
use DateTime; | |||
use OC\L10N\Factory; | |||
use OC\L10N\L10N; | |||
use Test\TestCase; | |||
/** | |||
* Class L10nTest | |||
* | |||
* @package Test\L10N | |||
*/ | |||
class L10nTest extends TestCase { | |||
/** | |||
* @return Factory | |||
*/ | |||
protected function getFactory() { | |||
/** @var \OCP\IConfig $config */ | |||
$config = $this->getMock('OCP\IConfig'); | |||
/** @var \OCP\IRequest $request */ | |||
$request = $this->getMock('OCP\IRequest'); | |||
return new Factory($config, $request); | |||
} | |||
public function testGermanPluralTranslations() { | |||
$transFile = \OC::$SERVERROOT.'/tests/data/l10n/de.json'; | |||
$l = new L10N($this->getFactory(), 'test', 'de', [$transFile]); | |||
$this->assertEquals('1 Datei', (string) $l->n('%n file', '%n files', 1)); | |||
$this->assertEquals('2 Dateien', (string) $l->n('%n file', '%n files', 2)); | |||
} | |||
public function testRussianPluralTranslations() { | |||
$transFile = \OC::$SERVERROOT.'/tests/data/l10n/ru.json'; | |||
$l = new L10N($this->getFactory(), 'test', 'ru', [$transFile]); | |||
$this->assertEquals('1 файл', (string)$l->n('%n file', '%n files', 1)); | |||
$this->assertEquals('2 файла', (string)$l->n('%n file', '%n files', 2)); | |||
$this->assertEquals('6 файлов', (string)$l->n('%n file', '%n files', 6)); | |||
$this->assertEquals('21 файл', (string)$l->n('%n file', '%n files', 21)); | |||
$this->assertEquals('22 файла', (string)$l->n('%n file', '%n files', 22)); | |||
$this->assertEquals('26 файлов', (string)$l->n('%n file', '%n files', 26)); | |||
/* | |||
1 file 1 файл 1 папка | |||
2-4 files 2-4 файла 2-4 папки | |||
5-20 files 5-20 файлов 5-20 папок | |||
21 files 21 файл 21 папка | |||
22-24 files 22-24 файла 22-24 папки | |||
25-30 files 25-30 файлов 25-30 папок | |||
etc | |||
100 files 100 файлов, 100 папок | |||
1000 files 1000 файлов 1000 папок | |||
*/ | |||
} | |||
public function testCzechPluralTranslations() { | |||
$transFile = \OC::$SERVERROOT.'/tests/data/l10n/cs.json'; | |||
$l = new L10N($this->getFactory(), 'test', 'cs', [$transFile]); | |||
$this->assertEquals('1 okno', (string)$l->n('%n window', '%n windows', 1)); | |||
$this->assertEquals('2 okna', (string)$l->n('%n window', '%n windows', 2)); | |||
$this->assertEquals('5 oken', (string)$l->n('%n window', '%n windows', 5)); | |||
} | |||
public function localizationData() { | |||
return array( | |||
// timestamp as string | |||
array('February 13, 2009 at 11:31:30 PM GMT+0', 'en', 'datetime', '1234567890'), | |||
array('13. Februar 2009 um 23:31:30 GMT+0', 'de', 'datetime', '1234567890'), | |||
array('February 13, 2009', 'en', 'date', '1234567890'), | |||
array('13. Februar 2009', 'de', 'date', '1234567890'), | |||
array('11:31:30 PM GMT+0', 'en', 'time', '1234567890'), | |||
array('23:31:30 GMT+0', 'de', 'time', '1234567890'), | |||
// timestamp as int | |||
array('February 13, 2009 at 11:31:30 PM GMT+0', 'en', 'datetime', 1234567890), | |||
array('13. Februar 2009 um 23:31:30 GMT+0', 'de', 'datetime', 1234567890), | |||
array('February 13, 2009', 'en', 'date', 1234567890), | |||
array('13. Februar 2009', 'de', 'date', 1234567890), | |||
array('11:31:30 PM GMT+0', 'en', 'time', 1234567890), | |||
array('23:31:30 GMT+0', 'de', 'time', 1234567890), | |||
// DateTime object | |||
array('February 13, 2009 at 11:31:30 PM GMT+0', 'en', 'datetime', new DateTime('@1234567890')), | |||
array('13. Februar 2009 um 23:31:30 GMT+0', 'de', 'datetime', new DateTime('@1234567890')), | |||
array('February 13, 2009', 'en', 'date', new DateTime('@1234567890')), | |||
array('13. Februar 2009', 'de', 'date', new DateTime('@1234567890')), | |||
array('11:31:30 PM GMT+0', 'en', 'time', new DateTime('@1234567890')), | |||
array('23:31:30 GMT+0', 'de', 'time', new DateTime('@1234567890')), | |||
// en_GB | |||
array('13 February 2009 at 23:31:30 GMT+0', 'en_GB', 'datetime', new DateTime('@1234567890')), | |||
array('13 February 2009', 'en_GB', 'date', new DateTime('@1234567890')), | |||
array('23:31:30 GMT+0', 'en_GB', 'time', new DateTime('@1234567890')), | |||
array('13 February 2009 at 23:31:30 GMT+0', 'en-GB', 'datetime', new DateTime('@1234567890')), | |||
array('13 February 2009', 'en-GB', 'date', new DateTime('@1234567890')), | |||
array('23:31:30 GMT+0', 'en-GB', 'time', new DateTime('@1234567890')), | |||
); | |||
} | |||
/** | |||
* @dataProvider localizationData | |||
*/ | |||
public function testNumericStringLocalization($expectedDate, $lang, $type, $value) { | |||
$l = new L10N($this->getFactory(), 'test', $lang, []); | |||
$this->assertSame($expectedDate, $l->l($type, $value)); | |||
} | |||
public function firstDayData() { | |||
return array( | |||
array(1, 'de'), | |||
array(0, 'en'), | |||
); | |||
} | |||
/** | |||
* @dataProvider firstDayData | |||
* @param $expected | |||
* @param $lang | |||
*/ | |||
public function testFirstWeekDay($expected, $lang) { | |||
$l = new L10N($this->getFactory(), 'test', $lang, []); | |||
$this->assertSame($expected, $l->l('firstday', 'firstday')); | |||
} | |||
public function jsDateData() { | |||
return array( | |||
array('dd.MM.yy', 'de'), | |||
array('M/d/yy', 'en'), | |||
); | |||
} | |||
/** | |||
* @dataProvider jsDateData | |||
* @param $expected | |||
* @param $lang | |||
*/ | |||
public function testJSDate($expected, $lang) { | |||
$l = new L10N($this->getFactory(), 'test', $lang, []); | |||
$this->assertSame($expected, $l->l('jsdate', 'jsdate')); | |||
} | |||
public function testFactoryGetLanguageCode() { | |||
$l = $this->getFactory()->get('lib', 'de'); | |||
$this->assertEquals('de', $l->getLanguageCode()); | |||
} | |||
public function testServiceGetLanguageCode() { | |||
$l = \OC::$server->getL10N('lib', 'de'); | |||
$this->assertEquals('de', $l->getLanguageCode()); | |||
} | |||
} |
@@ -36,6 +36,46 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase { | |||
static protected $realDatabase = null; | |||
static private $wasDatabaseAllowed = false; | |||
/** @var array */ | |||
protected $services = []; | |||
/** | |||
* @param string $name | |||
* @param mixed $newService | |||
* @return bool | |||
*/ | |||
public function overwriteService($name, $newService) { | |||
if (isset($this->services[$name])) { | |||
return false; | |||
} | |||
$this->services[$name] = \OC::$server->query($name); | |||
\OC::$server->registerService($name, function () use ($newService) { | |||
return $newService; | |||
}); | |||
return true; | |||
} | |||
/** | |||
* @param string $name | |||
* @return bool | |||
*/ | |||
public function restoreService($name) { | |||
if (isset($this->services[$name])) { | |||
$oldService = $this->services[$name]; | |||
\OC::$server->registerService($name, function () use ($oldService) { | |||
return $oldService; | |||
}); | |||
unset($this->services[$name]); | |||
return true; | |||
} | |||
return false; | |||
} | |||
protected function getTestTraits() { | |||
$traits = []; | |||
$class = $this; |