diff --git a/apps/files_sharing/tests/activity.php b/apps/files_sharing/tests/activity.php index b3575b0b70..40a1031f77 100644 --- a/apps/files_sharing/tests/activity.php +++ b/apps/files_sharing/tests/activity.php @@ -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() ); diff --git a/core/js/config.php b/core/js/config.php index 0670df60ab..708da777ee 100644 --- a/core/js/config.php +++ b/core/js/config.php @@ -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')), diff --git a/lib/private/l10n/factory.php b/lib/private/l10n/factory.php index c3c7cc21bb..09496cba41 100644 --- a/lib/private/l10n/factory.php +++ b/lib/private/l10n/factory.php @@ -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 = array(); + protected $instances = []; + + /** + * @var array Structure: App => string[] + */ + protected $availableLanguages = []; + + /** + * @var array Structure: string => callable + */ + 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; + } + } } diff --git a/lib/private/l10n/l10n.php b/lib/private/l10n/l10n.php new file mode 100644 index 0000000000..3e999e8c67 --- /dev/null +++ b/lib/private/l10n/l10n.php @@ -0,0 +1,216 @@ + + * + * @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 + * + */ + +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; + } +} diff --git a/lib/private/l10n/string.php b/lib/private/l10n/string.php index 84d1603871..9c93b8c5a6 100644 --- a/lib/private/l10n/string.php +++ b/lib/private/l10n/string.php @@ -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(); } - } diff --git a/lib/private/l10n.php b/lib/private/legacy/l10n.php similarity index 53% rename from lib/private/l10n.php rename to lib/private/legacy/l10n.php index b53fadc2bd..5d5d89100a 100644 --- a/lib/private/l10n.php +++ b/lib/private/legacy/l10n.php @@ -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; + + if ($lang !== null) { + $lang = str_replace(array('\0', '/', '\\', '..'), '', $lang); + } + + // Find the right language + if ($app !== 'test' && !\OC::$server->getL10NFactory()->languageExists($app, $lang)) { + $lang = \OC::$server->getL10NFactory()->findLanguage($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; - } - } - } - } - - self::$language = 'en'; - // Last try: English - return 'en'; - } - /** * @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,7 +272,38 @@ class OC_L10N implements \OCP\IL10N { * @return string language */ public function getLanguageCode() { - return $this->lang ? $this->lang : self::findLanguage(); + return $this->lang; + } + + /** + * @return string + * @throws \Punic\Exception\ValueNotInList + * @deprecated 9.0.0 Use $this->l('jsdate', null) instead + */ + public function getDateFormat() { + $locale = $this->transformToCLDRLocale($this->getLanguageCode()); + return Punic\Calendar::getDateFormat('short', $locale); + } + + /** + * @return int + * @deprecated 9.0.0 Use $this->l('firstday', null) instead + */ + public function getFirstWeekDay() { + $locale = $this->transformToCLDRLocale($this->getLanguageCode()); + return Punic\Calendar::getFirstWeekday($locale); + } + + /** + * @param string $locale + * @return string + */ + private function transformToCLDRLocale($locale) { + if ($locale === 'sr@latin') { + return 'sr_latn'; + } + + return $locale; } /** @@ -397,123 +312,37 @@ class OC_L10N implements \OCP\IL10N { * @return string language * * If nothing works it returns 'en' + * @deprecated 9.0.0 Use \OC::$server->getL10NFactory()->findLanguage() 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; + return \OC::$server->getL10NFactory()->findLanguage($app); } /** - * find the l10n directory - * @param string $app App that needs to be translated - * @return string directory + * @return string + * @deprecated 9.0.0 Use \OC::$server->getL10NFactory()->setLanguageFromRequest() 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 static function setLanguageFromRequest() { + return \OC::$server->getL10NFactory()->setLanguageFromRequest(); } /** * 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 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; - } - } - } - - self::$availableLanguages[$app] = $available; - return $available; + return \OC::$server->getL10NFactory()->findAvailableLanguages($app); } /** * @param string $app * @param string $lang * @return bool + * @deprecated 9.0.0 Use \OC::$server->getL10NFactory()->languageExists() 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; - } - - /** - * @return string - * @throws \Punic\Exception\ValueNotInList - */ - public function getDateFormat() { - $locale = $this->getLanguageCode(); - $locale = $this->transformToCLDRLocale($locale); - return Punic\Calendar::getDateFormat('short', $locale); - } - - /** - * @return int - */ - public function getFirstWeekDay() { - $locale = $this->getLanguageCode(); - $locale = $this->transformToCLDRLocale($locale); - return Punic\Calendar::getFirstWeekday($locale); - } - - private function transformToCLDRLocale($locale) { - if ($locale === 'sr@latin') { - return 'sr_latn'; - } - - return $locale; + return \OC::$server->getL10NFactory()->languageExists($app, $lang); } } diff --git a/lib/private/server.php b/lib/private/server.php index b8f4bdb53f..81929d0c7b 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -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(); diff --git a/lib/public/l10n/ifactory.php b/lib/public/l10n/ifactory.php index fa3f84fa2f..264c971963 100644 --- a/lib/public/l10n/ifactory.php +++ b/lib/public/l10n/ifactory.php @@ -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); } diff --git a/ocs/v1.php b/ocs/v1.php index 4371b0b604..39a8f4e468 100644 --- a/ocs/v1.php +++ b/ocs/v1.php @@ -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) { diff --git a/remote.php b/remote.php index a145fc4bd8..26203e2df8 100644 --- a/remote.php +++ b/remote.php @@ -109,7 +109,7 @@ try { } // force language as given in the http request - \OC_L10N::setLanguageFromRequest(); + \OC::$server->getL10NFactory()->setLanguageFromRequest(); $file=ltrim($file, '/'); diff --git a/settings/ajax/setlanguage.php b/settings/ajax/setlanguage.php index 760d2ca5d7..537a5afe95 100644 --- a/settings/ajax/setlanguage.php +++ b/settings/ajax/setlanguage.php @@ -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 ); diff --git a/settings/personal.php b/settings/personal.php index 11b4f762a3..261a459a92 100644 --- a/settings/personal.php +++ b/settings/personal.php @@ -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( diff --git a/tests/lib/l10n/factorytest.php b/tests/lib/l10n/factorytest.php new file mode 100644 index 0000000000..f632e48e2d --- /dev/null +++ b/tests/lib/l10n/factorytest.php @@ -0,0 +1,310 @@ + + * 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)); + } +} diff --git a/tests/lib/l10n.php b/tests/lib/l10n/l10nlegacytest.php similarity index 76% rename from tests/lib/l10n.php rename to tests/lib/l10n/l10nlegacytest.php index d77548c5bf..ae84968e65 100644 --- a/tests/lib/l10n.php +++ b/tests/lib/l10n/l10nlegacytest.php @@ -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()); } diff --git a/tests/lib/l10n/l10ntest.php b/tests/lib/l10n/l10ntest.php new file mode 100644 index 0000000000..95546b4f78 --- /dev/null +++ b/tests/lib/l10n/l10ntest.php @@ -0,0 +1,162 @@ + + * 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()); + } +} diff --git a/tests/lib/testcase.php b/tests/lib/testcase.php index 38d5cf4932..008b96b341 100644 --- a/tests/lib/testcase.php +++ b/tests/lib/testcase.php @@ -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;