Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/server.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Müller <thomas.mueller@tmit.eu>2016-01-27 11:37:00 +0300
committerThomas Müller <thomas.mueller@tmit.eu>2016-01-27 11:37:00 +0300
commit1594371c8c0124af2469bd70b9b917bfd845ae19 (patch)
treea7fb0546707e05984fa78f45a53563514b9c9d4d
parent5b7a1b13f3514f60650e1da41d8dc046ee0252c5 (diff)
parent3a6390031cb7e6575edd8d4a5271614eb8ba9470 (diff)
Merge pull request #21741 from owncloud/l10n-improvements
Move methods to the factory that are not related to translating, but to guessing/finding the language
-rw-r--r--apps/files_sharing/tests/activity.php8
-rw-r--r--core/js/config.php4
-rw-r--r--lib/private/l10n/factory.php291
-rw-r--r--lib/private/l10n/l10n.php216
-rw-r--r--lib/private/l10n/string.php22
-rw-r--r--lib/private/legacy/l10n.php (renamed from lib/private/l10n.php)309
-rw-r--r--lib/private/server.php7
-rw-r--r--lib/public/l10n/ifactory.php43
-rw-r--r--ocs/v1.php2
-rw-r--r--remote.php2
-rw-r--r--settings/ajax/setlanguage.php2
-rw-r--r--settings/personal.php2
-rw-r--r--tests/lib/l10n/factorytest.php310
-rw-r--r--tests/lib/l10n/l10nlegacytest.php (renamed from tests/lib/l10n.php)63
-rw-r--r--tests/lib/l10n/l10ntest.php162
-rw-r--r--tests/lib/testcase.php40
16 files changed, 1167 insertions, 316 deletions
diff --git a/apps/files_sharing/tests/activity.php b/apps/files_sharing/tests/activity.php
index b3575b0b709..40a1031f779 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 0670df60abd..708da777ee4 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 c3c7cc21bba..09496cba410 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 = [];
+
+ /**
+ * @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;
+ }
+ }
}
diff --git a/lib/private/l10n/l10n.php b/lib/private/l10n/l10n.php
new file mode 100644
index 00000000000..3e999e8c671
--- /dev/null
+++ b/lib/private/l10n/l10n.php
@@ -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;
+ }
+}
diff --git a/lib/private/l10n/string.php b/lib/private/l10n/string.php
index 84d1603871f..9c93b8c5a64 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
index b53fadc2bdd..5d5d89100ac 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;
- $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);
}
}
diff --git a/lib/private/server.php b/lib/private/server.php
index b8f4bdb53fe..81929d0c7b3 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 fa3f84fa2fd..264c9719639 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 4371b0b604a..39a8f4e468f 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 a145fc4bd84..26203e2df8a 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 760d2ca5d7d..537a5afe958 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 11b4f762a36..261a459a921 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 00000000000..f632e48e2de
--- /dev/null
+++ b/tests/lib/l10n/factorytest.php
@@ -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));
+ }
+}
diff --git a/tests/lib/l10n.php b/tests/lib/l10n/l10nlegacytest.php
index d77548c5bf5..ae84968e65d 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 00000000000..95546b4f788
--- /dev/null
+++ b/tests/lib/l10n/l10ntest.php
@@ -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());
+ }
+}
diff --git a/tests/lib/testcase.php b/tests/lib/testcase.php
index 38d5cf49320..008b96b3417 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;