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