# Repository management
.svn
+# git-deploy status file:
+/.deploy
+
# Editors
*.kate-swp
*~
* (bug 39957) Added $wgUnwatchedPageThreshold, specifying minimum count
of page watchers required for the number to be accessible to users
without the unwatchedpages permission.
-* $wgPageInfoTransclusionLimit limits the list size of transcluded articles
- on the info action. Default is 50.
=== New features in 1.21 ===
* (bug 38110) Schema changes (adding or dropping tables, indicies and
* New preference type - 'api'. Preferences of this type are not shown on
Special:Preferences, but are still available via the action=options API.
* (bug 39397) Hide rollback link if a user is the only contributor of the page.
+* $wgPageInfoTransclusionLimit limits the list size of transcluded articles
+ on the info action. Default is 50.
+* Added action=createaccount to allow user account creation.
=== Bug fixes in 1.21 ===
* (bug 40353) SpecialDoubleRedirect should support interwiki redirects.
&$changeslist: The OldChangesList instance.
&$s: HTML of the form "<li>...</li>" containing one RC entry.
&$rc: The RecentChange object.
+&$classes: array of css classes for the <li> element
'OpenSearchUrls': Called when constructing the OpenSearch description XML. Hooks
can alter or append to the array of URLs for search & suggestion formats.
*
* @note code that wants to retrieve page content from the database should use WikiPage::getContent().
*
- * @return Content|null
+ * @return Content|null|boolean false
*
* @since 1.21
*/
'ApiBase' => 'includes/api/ApiBase.php',
'ApiBlock' => 'includes/api/ApiBlock.php',
'ApiComparePages' => 'includes/api/ApiComparePages.php',
+ 'ApiCreateAccount' => 'includes/api/ApiCreateAccount.php',
'ApiDelete' => 'includes/api/ApiDelete.php',
'ApiDisabled' => 'includes/api/ApiDisabled.php',
'ApiEditPage' => 'includes/api/ApiEditPage.php',
$classes[] = Sanitizer::escapeClass( 'watchlist-'.$rc->mAttribs['rc_namespace'].'-'.$rc->mAttribs['rc_title'] );
}
- if ( !wfRunHooks( 'OldChangesListRecentChangesLine', array( &$this, &$s, $rc ) ) ) {
+ if ( !wfRunHooks( 'OldChangesListRecentChangesLine', array( &$this, &$s, $rc, &$classes ) ) ) {
wfProfileOut( __METHOD__ );
return false;
}
* Find the object with a given name and return it (or NULL)
*
* @param $name String Special page name, may be localised and/or an alias
- * @return SpecialPage object or null if the page doesn't exist
+ * @return SpecialPage|null SpecialPage object or null if the page doesn't exist
*/
public static function getPage( $name ) {
list( $realName, /*...*/ ) = self::resolveAlias( $name );
--- /dev/null
+<?php
+/**
+ * Created on August 7, 2012
+ *
+ * Copyright © 2012 Tyler Romeo <tylerromeo@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Unit to authenticate account registration attempts to the current wiki.
+ *
+ * @ingroup API
+ */
+class ApiCreateAccount extends ApiBase {
+ public function execute() {
+ $params = $this->extractRequestParams();
+
+ $result = array();
+
+ // Init session if necessary
+ if ( session_id() == '' ) {
+ wfSetupSession();
+ }
+
+ if( $params['mailpassword'] && !$params['email'] ) {
+ $this->dieUsageMsg( 'noemail' );
+ }
+
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setRequest( new DerivativeRequest(
+ $this->getContext()->getRequest(),
+ array(
+ 'type' => 'signup',
+ 'uselang' => $params['language'],
+ 'wpName' => $params['name'],
+ 'wpPassword' => $params['password'],
+ 'wpRetype' => $params['password'],
+ 'wpDomain' => $params['domain'],
+ 'wpEmail' => $params['email'],
+ 'wpRealName' => $params['realname'],
+ 'wpCreateaccountToken' => $params['token'],
+ 'wpCreateaccount' => $params['mailpassword'] ? null : '1',
+ 'wpCreateaccountMail' => $params['mailpassword'] ? '1' : null
+ )
+ ) );
+
+ $loginForm = new LoginForm();
+ $loginForm->setContext( $context );
+ $loginForm->load();
+
+ $status = $loginForm->addNewaccountInternal();
+ $result = array();
+ if( $status->isGood() ) {
+ // Success!
+ $user = $status->getValue();
+
+ // If we showed up language selection links, and one was in use, be
+ // smart (and sensible) and save that language as the user's preference
+ global $wgLoginLanguageSelector, $wgEmailAuthentication;
+ if( $wgLoginLanguageSelector && $params['language'] ) {
+ $user->setOption( 'language', $params['language'] );
+ }
+
+ if( $params['mailpassword'] ) {
+ // If mailpassword was set, disable the password and send an email.
+ $user->setPassword( null );
+ $status->merge( $loginForm->mailPasswordInternal( $user, false, 'createaccount-title', 'createaccount-text' ) );
+ } elseif( $wgEmailAuthentication && Sanitizer::validateEmail( $user->getEmail() ) ) {
+ // Send out an email authentication message if needed
+ $status->merge( $user->sendConfirmationMail() );
+ }
+
+ // Save settings (including confirmation token)
+ $user->saveSettings();
+
+ wfRunHooks( 'AddNewAccount', array( $user, false ) );
+ $user->addNewUserLogEntry( $this->getUser()->isAnon(), $params['reason'] );
+
+ // Add username, id, and token to result.
+ $result['username'] = $user->getName();
+ $result['userid'] = $user->getId();
+ $result['token'] = $user->getToken();
+ }
+
+ $apiResult = $this->getResult();
+
+ if( $status->hasMessage( 'sessionfailure' ) ) {
+ // Token was incorrect, so add it to result, but don't throw an exception.
+ $result['token'] = LoginForm::getCreateaccountToken();
+ $result['result'] = 'needtoken';
+ } elseif( !$status->isOK() ) {
+ // There was an error. Die now.
+ // Cannot use dieUsageMsg() directly because extensions
+ // might return custom error messages.
+ $errors = $status->getErrorsArray();
+ if( $errors[0] instanceof Message ) {
+ $code = 'aborted';
+ $desc = $errors[0];
+ } else {
+ $code = array_shift( $errors[0] );
+ $desc = wfMessage( $code, $errors[0] );
+ }
+ $this->dieUsage( $desc, $code );
+ } elseif( !$status->isGood() ) {
+ // Status is not good, but OK. This means warnings.
+ $result['result'] = 'warning';
+
+ // Add any warnings to the result
+ $warnings = $status->getErrorsByType( 'warning' );
+ if( $warnings ) {
+ foreach( $warnings as &$warning ) {
+ $apiResult->setIndexedTagName( $warning['params'], 'param' );
+ }
+ $apiResult->setIndexedTagName( $warnings, 'warning' );
+ $result['warnings'] = $warnings;
+ }
+ } else {
+ // Everything was fine.
+ $result['result'] = 'success';
+ }
+
+ $apiResult->addValue( null, 'createaccount', $result );
+ }
+
+ public function getDescription() {
+ return 'Create a new user account.';
+ }
+
+ public function mustBePosted() {
+ return true;
+ }
+
+ public function isReadMode() {
+ return false;
+ }
+
+ public function isWriteMode() {
+ return true;
+ }
+
+ public function getAllowedParams() {
+ global $wgEmailConfirmToEdit;
+ return array(
+ 'name' => array(
+ ApiBase::PARAM_TYPE => 'user',
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ 'password' => null,
+ 'domain' => null,
+ 'token' => null,
+ 'email' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => $wgEmailConfirmToEdit
+ ),
+ 'realname' => null,
+ 'mailpassword' => array(
+ ApiBase::PARAM_TYPE => 'boolean',
+ ApiBase::PARAM_DFLT => false
+ ),
+ 'reason' => null,
+ 'language' => null
+ );
+ }
+
+ public function getParamDescription() {
+ $p = $this->getModulePrefix();
+ return array(
+ 'name' => 'User Name',
+ 'password' => "Password (ignored if {$p}mailpassword is set)",
+ 'domain' => 'Domain (optional)',
+ 'token' => 'Account creation token obtained in first request',
+ 'email' => 'Email address of user',
+ 'realname' => 'Real Name of user',
+ 'mailpassword' => 'Whether to generate and mail a random password to the user',
+ 'reason' => "Optional reason for creating the account (used when {$p}mailpassword is set)",
+ 'language' => 'Language code to set for the user.'
+ );
+ }
+
+ public function getResultProperties() {
+ return array(
+ 'createaccount' => array(
+ 'result' => array(
+ ApiBase::PROP_TYPE => array(
+ 'success',
+ 'warning',
+ 'needtoken'
+ )
+ ),
+ 'username' => array(
+ ApiBase::PROP_TYPE => 'string',
+ ApiBase::PROP_NULLABLE => true
+ ),
+ 'userid' => array(
+ ApiBase::PROP_TYPE => 'int',
+ ApiBase::PROP_NULLABLE => true
+ ),
+ 'token' => array(
+ ApiBase::PROP_TYPE => 'string',
+ ApiBase::PROP_NULLABLE => true
+ ),
+ )
+ );
+ }
+
+ public function getPossibleErrors() {
+ $localErrors = array(
+ 'wrongpassword',
+ 'sessionfailure',
+ 'sorbs_create_account_reason',
+ 'noname',
+ 'userexists',
+ 'password-name-match',
+ 'password-login-forbidden',
+ 'noemailtitle',
+ 'invalidemailaddress',
+ 'externaldberror'
+ );
+
+ $errors = parent::getPossibleErrors();
+ // All local errors are from LoginForm, which means they're actually message keys.
+ foreach( $localErrors as $error ) {
+ $errors[] = array( 'code' => $error, 'info' => wfMessage( $error )->parse() );
+ }
+
+ // 'passwordtooshort' has parameters. :(
+ global $wgMinimalPasswordLength;
+ $errors[] = array(
+ 'code' => 'passwordtooshort',
+ 'info' => wfMessage( 'passwordtooshort', $wgMinimalPasswordLength )->parse()
+ );
+ return $errors;
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=createaccount&name=testuser&password=test123',
+ 'api.php?action=createaccount&name=testmailuser&mailpassword=true&reason=MyReason',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Account creation';
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id$';
+ }
+}
private static $Modules = array(
'login' => 'ApiLogin',
'logout' => 'ApiLogout',
+ 'createaccount' => 'ApiCreateAccount',
'query' => 'ApiQuery',
'expandtemplates' => 'ApiExpandTemplates',
'parse' => 'ApiParse',
$content = null;
global $wgParser;
- if ( $this->fld_content || !is_null( $this->difftotext ) ) {
+ if ( $this->fld_content || !is_null( $this->diffto ) || !is_null( $this->difftotext ) ) {
$content = $revision->getContent();
// Expand templates after getting section content because
// template-added sections don't count and Parser::preprocess()
if ( $result === false ) {
// Database connection was in "ignoreErrors" mode. We don't like that.
// So, we emulate the DBQueryError that should have been thrown.
- $error = new \DBQueryError(
+ $error = new DBQueryError(
$dbr,
$dbr->lastError(),
$dbr->lastErrno(),
abstract protected function doGetAcquiredCount();
/**
- * Push a batch of jobs into the queue
+ * Push a batch of jobs into the queue.
+ * This does not require $wgJobClasses to be set for the given job type.
*
* @param $jobs array List of Jobs
* @param $flags integer Bitfield (supports JobQueue::QoS_Atomic)
abstract protected function doBatchPush( array $jobs, $flags );
/**
- * Pop a job off of the queue
+ * Pop a job off of the queue.
+ * This requires $wgJobClasses to be set for the given job type.
*
* @return Job|bool Returns false on failure
*/
$lb = ( $this->cluster !== false )
? wfGetLBFactory()->getExternalLB( $this->cluster, $this->wiki )
: wfGetLB( $this->wiki );
- $conn = $lb->getConnection( $index );
+ $conn = $lb->getConnection( $index, array(), $this->wiki );
return array(
$conn,
new ScopedCallback( function() use ( $lb, $conn ) {
}
/**
- * Insert jobs into the respective queues of with the belong
+ * Insert jobs into the respective queues of with the belong.
+ * This inserts the jobs into the queue specified by $wgJobTypeConf.
*
* @param $jobs Job|array A single Job or a list of Jobs
* @throws MWException
if ( !isset( $this->params['usleep'] ) ) {
$this->params['usleep'] = 0;
}
+ $this->removeDuplicates = !empty( $this->params['removeDuplicates'] );
}
public function run() {
class APCBagOStuff extends BagOStuff {
/**
* @param $key string
+ * @param $casToken[optional] int
* @return mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
$val = apc_fetch( $key );
+ $casToken = $val;
+
if ( is_string( $val ) ) {
if ( $this->isInteger( $val ) ) {
$val = intval( $val );
return true;
}
+ /**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ // APC's CAS functions only work on integers
+ throw new MWException( "CAS is not implemented in " . __CLASS__ );
+ }
+
/**
* @param $key string
* @param $time int
return true;
}
+ /**
+ * @param $key string
+ * @param $callback closure Callback method to be executed
+ * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
+ * @param $attempts int The amount of times to attempt a merge in case of failure
+ * @return bool success
+ */
+ public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ return $this->mergeViaLock( $key, $callback, $exptime, $attempts );
+ }
+
public function incr( $key, $value = 1 ) {
return apc_inc( $key, $value );
}
/**
* Get an item with the given key. Returns false if it does not exist.
* @param $key string
+ * @param $casToken[optional] mixed
* @return mixed Returns false on failure
*/
- abstract public function get( $key );
+ abstract public function get( $key, &$casToken = null );
/**
* Set an item.
*/
abstract public function set( $key, $value, $exptime = 0 );
+ /**
+ * Check and set an item.
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
+ * @return bool success
+ */
+ abstract public function cas( $casToken, $key, $value, $exptime = 0 );
+
/**
* Delete an item.
* @param $key string
abstract public function delete( $key, $time = 0 );
/**
+ * Merge changes into the existing cache value (possibly creating a new one)
+ *
* @param $key string
- * @param $timeout integer
+ * @param $callback closure Callback method to be executed
+ * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
+ * @param $attempts int The amount of times to attempt a merge in case of failure
* @return bool success
*/
- public function lock( $key, $timeout = 0 ) {
- /* stub */
- return true;
+ public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ return $this->mergeViaCas( $key, $callback, $exptime, $attempts );
+ }
+
+ /**
+ * @see BagOStuff::merge()
+ *
+ * @param $key string
+ * @param $callback closure Callback method to be executed
+ * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
+ * @param $attempts int The amount of times to attempt a merge in case of failure
+ * @return bool success
+ */
+ protected function mergeViaCas( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ do {
+ $casToken = null; // passed by reference
+ $currentValue = $this->get( $key, $casToken ); // get the old value
+ $value = $callback( $this, $key, $currentValue ); // derive the new value
+
+ if ( $value === false ) {
+ $success = true; // do nothing
+ } elseif ( $currentValue === false ) {
+ // Try to create the key, failing if it gets created in the meantime
+ $success = $this->add( $key, $value, $exptime );
+ } else {
+ // Try to update the key, failing if it gets changed in the meantime
+ $success = $this->cas( $casToken, $key, $value, $exptime );
+ }
+ } while ( !$success && --$attempts );
+
+ return $success;
+ }
+
+ /**
+ * @see BagOStuff::merge()
+ *
+ * @param $key string
+ * @param $callback closure Callback method to be executed
+ * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
+ * @param $attempts int The amount of times to attempt a merge in case of failure
+ * @return bool success
+ */
+ protected function mergeViaLock( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ if ( !$this->lock( $key, 60 ) ) {
+ return false;
+ }
+
+ $currentValue = $this->get( $key ); // get the old value
+ $value = $callback( $this, $key, $currentValue ); // derive the new value
+
+ if ( $value === false ) {
+ $success = true; // do nothing
+ } else {
+ $success = $this->set( $key, $value, $exptime ); // set the new value
+ }
+
+ if ( !$this->unlock( $key ) ) {
+ // this should never happen
+ trigger_error( "Could not release lock for key '$key'." );
+ }
+
+ return $success;
+ }
+
+ /**
+ * @param $key string
+ * @param $timeout integer [optional]
+ * @return bool success
+ */
+ public function lock( $key, $timeout = 60 ) {
+ $timestamp = microtime( true ); // starting UNIX timestamp
+ if ( $this->add( "{$key}:lock", $timeout ) ) {
+ return true;
+ }
+
+ $uRTT = ceil( 1e6 * ( microtime( true ) - $timestamp ) ); // estimate RTT (us)
+ $sleep = 2*$uRTT; // rough time to do get()+set()
+
+ $locked = false; // lock acquired
+ $attempts = 0; // failed attempts
+ do {
+ if ( ++$attempts >= 3 && $sleep <= 1e6 ) {
+ // Exponentially back off after failed attempts to avoid network spam.
+ // About 2*$uRTT*(2^n-1) us of "sleep" happen for the next n attempts.
+ $sleep *= 2;
+ }
+ usleep( $sleep ); // back off
+ $locked = $this->add( "{$key}:lock", $timeout );
+ } while( !$locked );
+
+ return $locked;
}
/**
* @return bool success
*/
public function unlock( $key ) {
- /* stub */
- return true;
+ return $this->delete( "{$key}:lock" );
}
/**
/**
* @param $key string
+ * @param $casToken[optional] mixed
* @return mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
wfProfileIn( __METHOD__ );
wfDebug( __METHOD__ . "($key)\n" );
$val = false;
}
+ $casToken = $val;
+
wfProfileOut( __METHOD__ );
+
return $val;
}
return $ret;
}
+ /**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ wfProfileIn( __METHOD__ );
+ wfDebug( __METHOD__ . "($key)\n" );
+
+ $blob = $this->encode( $value, $exptime );
+
+ $handle = $this->getWriter();
+ if ( !$handle ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ // DBA is locked to any other write connection, so we can safely
+ // compare the current & previous value before saving new value
+ $val = dba_fetch( $key, $handle );
+ list( $val, $exptime ) = $this->decode( $val );
+ if ( $casToken !== $val ) {
+ dba_close( $handle );
+ return false;
+ }
+
+ $ret = dba_replace( $key, $blob, $handle );
+ dba_close( $handle );
+
+ wfProfileOut( __METHOD__ );
+ return $ret;
+ }
+
/**
* @param $key string
* @param $time int
/**
* @param $key string
+ * @param $casToken[optional] mixed
* @return bool|mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
wfProfileIn( __METHOD__ );
$response = $this->doItemRequest( $key );
if ( !$response || $response['http_code'] == 404 ) {
return false;
}
+ $casToken = $body;
+
wfProfileOut( __METHOD__ );
return $data;
}
return $result;
}
+ /**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ // Not sure if we can implement CAS for ehcache. There appears to be CAS-support per
+ // http://ehcache.org/documentation/get-started/consistency-options#cas-cache-operations,
+ // but I can't find any docs for our current implementation.
+ throw new MWException( "CAS is not implemented in " . __CLASS__ );
+ }
+
/**
* @param $key string
* @param $time int
return $result;
}
+ /**
+ * @see BagOStuff::merge()
+ * @return bool success
+ */
+ public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ return $this->mergeViaLock( $key, $callback, $exptime, $attempts );
+ }
+
/**
* @param $key string
* @return string
/**
* @param $key string
+ * @param $casToken[optional] mixed
* @return bool
*/
- function get( $key ) {
+ function get( $key, &$casToken = null ) {
return false;
}
return true;
}
+ /**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exp int
+ * @return bool
+ */
+ function cas( $casToken, $key, $value, $exp = 0 ) {
+ return true;
+ }
+
/**
* @param $key string
* @param $time int
function delete( $key, $time = 0 ) {
return true;
}
+
+ /**
+ * @param $key string
+ * @param $callback closure Callback method to be executed
+ * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
+ * @param $attempts int The amount of times to attempt a merge in case of failure
+ * @return bool success
+ */
+ public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ return true;
+ }
}
/**
/**
* @param $key string
+ * @param $casToken[optional] mixed
* @return bool|mixed
*/
- function get( $key ) {
+ function get( $key, &$casToken = null ) {
if ( !isset( $this->bag[$key] ) ) {
return false;
}
return false;
}
+ $casToken = $this->bag[$key][0];
+
return $this->bag[$key][0];
}
return true;
}
+ /**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int
+ * @return bool
+ */
+ function cas( $casToken, $key, $value, $exptime = 0 ) {
+ if ( $this->get( $key ) === $casToken ) {
+ return $this->set( $key, $value, $exptime );
+ }
+
+ return false;
+ }
+
/**
* @param $key string
* @param $time int
/**
* @param $key string
+ * @param $casToken[optional] mixed
* @return Mixed
*/
- public function get( $key ) {
- return $this->client->get( $this->encodeKey( $key ) );
+ public function get( $key, &$casToken = null ) {
+ return $this->client->get( $this->encodeKey( $key ), $casToken );
}
/**
$this->fixExpiry( $exptime ) );
}
+ /**
+ * @param $key string
+ * @param $casToken mixed
+ * @param $value
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ return $this->client->cas( $casToken, $this->encodeKey( $key ),
+ $value, $this->fixExpiry( $exptime ) );
+ }
+
/**
* @param $key string
* @param $time int
* Retrieves the value associated with the key from the memcache server
*
* @param $key array|string key to retrieve
+ * @param $casToken[optional] Float
*
* @return Mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
wfProfileIn( __METHOD__ );
if ( $this->_debug ) {
$this->stats['get'] = 1;
}
- $cmd = "get $key\r\n";
+ $cmd = "gets $key\r\n";
if ( !$this->_fwrite( $sock, $cmd ) ) {
wfProfileOut( __METHOD__ );
return false;
}
$val = array();
- $this->_load_items( $sock, $val );
+ $this->_load_items( $sock, $val, $casToken );
if ( $this->_debug ) {
foreach ( $val as $k => $v ) {
$gather = array();
// Send out the requests
foreach ( $socks as $sock ) {
- $cmd = 'get';
+ $cmd = 'gets';
foreach ( $sock_keys[ intval( $sock ) ] as $key ) {
$cmd .= ' ' . $key;
}
// Parse responses
$val = array();
foreach ( $gather as $sock ) {
- $this->_load_items( $sock, $val );
+ $this->_load_items( $sock, $val, $casToken );
}
if ( $this->_debug ) {
return $this->_set( 'set', $key, $value, $exp );
}
+ // }}}
+ // {{{ cas()
+
+ /**
+ * Sets a key to a given value in the memcache if the current value still corresponds
+ * to a known, given value. Returns true if set successfully.
+ *
+ * @param $casToken Float: current known value
+ * @param $key String: key to set value as
+ * @param $value Mixed: value to set
+ * @param $exp Integer: (optional) Expiration time. This can be a number of seconds
+ * to cache for (up to 30 days inclusive). Any timespans of 30 days + 1 second or
+ * longer must be the timestamp of the time at which the mapping should expire. It
+ * is safe to use timestamps in all cases, regardless of exipration
+ * eg: strtotime("+3 hour")
+ *
+ * @return Boolean: TRUE on success
+ */
+ public function cas( $casToken, $key, $value, $exp = 0 ) {
+ return $this->_set( 'cas', $key, $value, $exp, $casToken );
+ }
+
// }}}
// {{{ set_compress_threshold()
*
* @param $sock Resource: socket to read from
* @param $ret Array: returned values
+ * @param $casToken[optional] Float
* @return boolean True for success, false for failure
*
* @access private
*/
- function _load_items( $sock, &$ret ) {
+ function _load_items( $sock, &$ret, &$casToken = null ) {
while ( 1 ) {
$decl = $this->_fgets( $sock );
if( $decl === false ) {
return false;
} elseif ( $decl == "END" ) {
return true;
- } elseif ( preg_match( '/^VALUE (\S+) (\d+) (\d+)$/', $decl, $match ) ) {
- list( $rkey, $flags, $len ) = array( $match[1], $match[2], $match[3] );
+ } elseif ( preg_match( '/^VALUE (\S+) (\d+) (\d+) (\d+)$/', $decl, $match ) ) {
+ list( $rkey, $flags, $len, $casToken ) = array( $match[1], $match[2], $match[3], $match[4] );
$data = $this->_fread( $sock, $len + 2 );
if ( $data === false ) {
return false;
* longer must be the timestamp of the time at which the mapping should expire. It
* is safe to use timestamps in all cases, regardless of exipration
* eg: strtotime("+3 hour")
+ * @param $casToken[optional] Float
*
* @return Boolean
* @access private
*/
- function _set( $cmd, $key, $val, $exp ) {
+ function _set( $cmd, $key, $val, $exp, $casToken = null ) {
if ( !$this->_active ) {
return false;
}
$flags |= self::COMPRESSED;
}
}
- if ( !$this->_fwrite( $sock, "$cmd $key $flags $exp $len\r\n$val\r\n" ) ) {
+
+ $command = "$cmd $key $flags $exp $len";
+ if ( $casToken ) {
+ $command .= " $casToken";
+ }
+
+ if ( !$this->_fwrite( $sock, "$command\r\n$val\r\n" ) ) {
return false;
}
/**
* @param $key string
+ * @param $casToken[optional] float
* @return Mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
wfProfileIn( __METHOD__ );
$this->debugLog( "get($key)" );
- $value = $this->checkResult( $key, parent::get( $key ) );
+ $result = $this->client->get( $this->encodeKey( $key ), null, $casToken );
+ $result = $this->checkResult( $key, $result );
wfProfileOut( __METHOD__ );
- return $value;
+ return $result;
}
/**
return $this->checkResult( $key, parent::set( $key, $value, $exptime ) );
}
+ /**
+ * @param $casToken float
+ * @param $key string
+ * @param $value
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ $this->debugLog( "cas($key)" );
+ return $this->checkResult( $key, parent::cas( $casToken, $key, $value, $exptime ) );
+ }
+
/**
* @param $key string
* @param $time int
/**
* @param $key string
+ * @param $casToken[optional] mixed
* @return bool|mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
foreach ( $this->caches as $cache ) {
$value = $cache->get( $key );
if ( $value !== false ) {
return false;
}
+ /**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ throw new MWException( "CAS is not implemented in " . __CLASS__ );
+ }
+
/**
* @param $key string
* @param $value mixed
}
}
+ /**
+ * @param $key string
+ * @param $callback closure Callback method to be executed
+ * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
+ * @param $attempts int The amount of times to attempt a merge in case of failure
+ * @return bool success
+ */
+ public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ return $this->doWrite( 'merge', $key, $callback, $exptime );
+ }
+
/**
* @param $method string
* @return bool
}
}
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
wfProfileIn( __METHOD__ );
list( $server, $conn ) = $this->getConnection( $key );
if ( !$conn ) {
$result = false;
$this->handleException( $server, $e );
}
+ $casToken = $result;
$this->logRequest( 'get', $key, $server, $result );
wfProfileOut( __METHOD__ );
return $result;
return $result;
}
+ /**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $expiry = 0 ) {
+ wfProfileIn( __METHOD__ );
+ list( $server, $conn ) = $this->getConnection( $key );
+ if ( !$conn ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ $expiry = $this->convertToRelative( $expiry );
+ try {
+ $conn->watch( $key );
+
+ if ( $this->get( $key ) !== $casToken ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ $conn->multi();
+
+ if ( !$expiry ) {
+ // No expiry, that is very different from zero expiry in Redis
+ $conn->set( $key, $value );
+ } else {
+ $conn->setex( $key, $expiry, $value );
+ }
+
+ $result = $conn->exec();
+ } catch ( RedisException $e ) {
+ $result = false;
+ $this->handleException( $server, $e );
+ }
+
+ $this->logRequest( 'cas', $key, $server, $result );
+ wfProfileOut( __METHOD__ );
+ return $result;
+ }
+
public function delete( $key, $time = 0 ) {
wfProfileIn( __METHOD__ );
list( $server, $conn ) = $this->getConnection( $key );
/**
* @param $key string
+ * @param $casToken[optional] mixed
* @return mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
$values = $this->getMulti( array( $key ) );
- return array_key_exists( $key, $values ) ? $values[$key] : false;
+ if ( array_key_exists( $key, $values ) ) {
+ $casToken = $values[$key];
+ return $values[$key];
+ }
+ return false;
}
/**
return true;
}
+ /**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ $db = $this->getDB();
+ $exptime = intval( $exptime );
+
+ if ( $exptime < 0 ) {
+ $exptime = 0;
+ }
+
+ if ( $exptime == 0 ) {
+ $encExpiry = $this->getMaxDateTime();
+ } else {
+ if ( $exptime < 3.16e8 ) { # ~10 years
+ $exptime += time();
+ }
+
+ $encExpiry = $db->timestamp( $exptime );
+ }
+ try {
+ $db->begin( __METHOD__ );
+ // (bug 24425) use a replace if the db supports it instead of
+ // delete/insert to avoid clashes with conflicting keynames
+ $db->update(
+ $this->getTableByKey( $key ),
+ array(
+ 'keyname' => $key,
+ 'value' => $db->encodeBlob( $this->serialize( $value ) ),
+ 'exptime' => $encExpiry
+ ),
+ array(
+ 'keyname' => $key,
+ 'value' => $db->encodeBlob( $this->serialize( $casToken ) )
+ ), __METHOD__ );
+ $db->commit( __METHOD__ );
+ } catch ( DBQueryError $e ) {
+ $this->handleWriteError( $e );
+
+ return false;
+ }
+
+ return (bool) $db->affectedRows();
+ }
+
/**
* @param $key string
* @param $time int
* Get a value from the WinCache object cache
*
* @param $key String: cache key
+ * @param $casToken[optional] int: cas token
* @return mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
$val = wincache_ucache_get( $key );
+ $casToken = $val;
+
if ( is_string( $val ) ) {
$val = unserialize( $val );
}
return ( is_array( $result ) && $result === array() ) || $result;
}
+ /**
+ * Store a value in the WinCache object cache, race condition-safe
+ *
+ * @param $casToken int: cas token
+ * @param $key String: cache key
+ * @param $value int: object to store
+ * @param $exptime Int: expiration time
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ return wincache_ucache_cas( $key, $casToken, serialize( $value ) );
+ }
+
/**
* Remove a value from the WinCache object cache
*
* Get a value from the XCache object cache
*
* @param $key String: cache key
+ * @param $casToken mixed: cas token
* @return mixed
*/
- public function get( $key ) {
+ public function get( $key, &$casToken = null ) {
$val = xcache_get( $key );
if ( is_string( $val ) ) {
return true;
}
+ /**
+ * @param $casToken mixed
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int
+ * @return bool
+ */
+ public function cas( $casToken, $key, $value, $exptime = 0 ) {
+ // Can't find any documentation on xcache cas
+ throw new MWException( "CAS is not implemented in " . __CLASS__ );
+ }
+
/**
* Remove a value from the XCache object cache
*
return true;
}
+ /**
+ * Merge an item.
+ * XCache does not seem to support any way of performing CAS - this however will
+ * provide a way to perform CAS-like functionality.
+ *
+ * @param $key string
+ * @param $callback closure Callback method to be executed
+ * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
+ * @param $attempts int The amount of times to attempt a merge in case of failure
+ * @return bool success
+ */
+ public function merge( $key, closure $callback, $exptime = 0, $attempts = 10 ) {
+ return $this->mergeViaLock( $key, $callback, $exptime, $attempts );
+ }
+
public function incr( $key, $value = 1 ) {
return xcache_inc( $key, $value );
}
'tog-externaleditor' => 'دَییشدیرمک اوچون ائشیک یازیلیم ایشلد (یالنیز چوخ باشارانلار اوچون، بیلگیسایارینیزدا مخصوص تنظیملر لازیمدیر. [//www.mediawiki.org/wiki/Manual:External_editors آرتیق بیلگیلر])',
'tog-externaldiff' => 'موقاییسه ائتمک اوچون ائشیک یازیلیم ایشلد (یالنیز چوخ باشارانلار اوچون، بیلگیسایارینیزدا مخصوص تنظیملر لازیمدیر. [//www.mediawiki.org/wiki/Manual:External_editors آرتیق بیلگیلر])',
'tog-showjumplinks' => '«آتلان:» یاردیم باغلانتیلارینی آچ',
-'tog-uselivepreview' => 'دÛ\8cرÛ\8c اؤÙ\86â\80\8cگؤسترÛ\8cØ´ اÛ\8cØ´Ù\84د (جاÙ\88ااسکرÛ\8cپت Ù\84ازÛ\8cÙ\85â\80\8cدÛ\8cر)(تست Ù\85رØÙ\84Ù\87â\80\8cسÛ\8cÙ\86دÙ\87)',
+'tog-uselivepreview' => 'دÛ\8cرÛ\8c اؤÙ\86â\80\8cگؤسترÛ\8cØ´ اÛ\8cØ´Ù\84ت (جاÙ\88ااسکرÛ\8cپت Ù\84ازÛ\8cÙ\85â\80\8cدÛ\8cر)',
'tog-forceeditsummary' => 'دَییشیکلیک قیساسی بوش قالاندا منی بیلدیر',
'tog-watchlisthideown' => 'منیم دَییشیکلیکلریمی ایزلهدیکلردن گیزلت',
'tog-watchlisthidebots' => 'بوت دَییشیکلیکلرینی ایزلهدیکلردن گیزلت',
'newwindow' => '(یئنی پنجرهده آچیلیر)',
'cancel' => 'لغو ائت',
'moredotdotdot' => 'داها...',
+'morenotlisted' => 'داها آرتیق لیست اولونماییبدیر...',
'mypage' => 'مقاله',
'mytalk' => 'دانیشیق',
'anontalk' => 'بو آیپی آدرسینه دانیشیق',
# E-mail sending
'php-mail-error-unknown' => 'پیاچپینین mail() فونکسیاسیندا تانینمامیش خطا.',
'user-mail-no-addy' => 'ایمیل آدرسی اولماماقلا، ایمیل گؤندرمگه چالیشدی',
+'user-mail-no-body' => 'بیر بوش یا چوخ قیسا یازیسی اولان ایمیل گؤندرمگه چالیشیلدی.',
# Change password dialog
'resetpass' => 'رمزی دَییشدیر',
'prot_1movedto2' => '[[$1]] آدی دییشیلدی. یئنی آدی: [[$2]]',
'protect-badnamespace-title' => 'آد سیز حفظ اولموش فضا',
'protect-badnamespace-text' => 'بو آد ساحهسیندکی صحیفهلر قورونا.',
+'protect-norestrictiontypes-text' => 'بو صحیفهنی قوروماق اولماز، چون هئچ بیر محدودلاشدیرما نؤوعو الده یوخدور.',
+'protect-norestrictiontypes-title' => 'قورونا-بیلمهین صحیفه',
'protect-legend' => 'قورومایی تصدیق ائت',
'protectcomment' => 'ندن:',
'protectexpiry' => 'زامان بیتدی',
'pageinfo-magic-words' => 'سیحیرلی {{PLURAL:$1|بیر|$1}} سؤزجوک ($1)',
'pageinfo-hidden-categories' => 'گیزلی {{PLURAL:$1|بؤلمه|بؤلمهلر}} ($1)',
'pageinfo-templates' => 'ایشلهدیلمیش {{PLURAL:$1|بیر|$1}} شابلون ($1)',
+'pageinfo-transclusions' => 'ایچینده گلن {{PLURAL:$1|صحیفه|صحیفهلر}} ($1)',
'pageinfo-toolboxlink' => 'صحیفه بیلگیسی',
'pageinfo-redirectsto' => 'ایستیقامتلندیریلن',
'pageinfo-redirectsto-info' => 'بیلگی',
'pageinfo-protect-cascading' => 'مدافعهلر بورادان شراره کیمی تؤکولور',
'pageinfo-protect-cascading-yes' => 'بلی',
'pageinfo-protect-cascading-from' => 'شراره مدافعهلر بورادان',
+'pageinfo-category-info' => 'بؤلمه بیلگیلری',
+'pageinfo-category-pages' => 'صحیفهلرین سایی',
+'pageinfo-category-subcats' => 'آلتبؤلمهلرین سایی',
+'pageinfo-category-files' => 'فایللارین سایی',
# Skin names
'skinname-myskin' => 'منیم قابیغیم',
'tog-externaleditor' => 'Выкарыстоўваць вонкавы рэдактар па змоўчваньні (толькі для адмыслоўцаў, патрабуе спэцыяльных наладак на вашым кампутары. [//www.mediawiki.org/wiki/Manual:External_editors Падрабязнасьці.])',
'tog-externaldiff' => 'Выкарыстоўваць вонкавую праграму параўнаньня вэрсіяў па змоўчваньні (толькі для адмыслоўцаў, патрабуе спэцыяльных наладак на вашым кампутары. [//www.mediawiki.org/wiki/Manual:External_editors Падрабязнасьці.])',
'tog-showjumplinks' => 'Актываваць дапаможныя спасылкі «перайсьці да»',
-'tog-uselivepreview' => 'Выкарыстоўваць хуткі папярэдні прагляд (патрабуе JavaScript) (экспэрымэнтальна)',
+'tog-uselivepreview' => 'Выкарыстоўваць хуткі папярэдні прагляд (патрабуе JavaScript)',
'tog-forceeditsummary' => 'Папярэджваць пра адсутнасьць кароткага апісаньня зьменаў',
'tog-watchlisthideown' => 'Хаваць мае праўкі ў сьпісе назіраньня',
'tog-watchlisthidebots' => 'Хаваць праўкі робатаў у сьпісе назіраньня',
'newwindow' => '(адкрываецца ў новым акне)',
'cancel' => 'Скасаваць',
'moredotdotdot' => 'Далей…',
+'morenotlisted' => 'Болей не паказанага...',
'mypage' => 'Старонка',
'mytalk' => 'Гутаркі',
'anontalk' => 'Гутаркі для гэтага IP-адрасу',
# E-mail sending
'php-mail-error-unknown' => 'Узьнікла невядомая памылка ў функцыі PHP mail()',
'user-mail-no-addy' => 'Спроба даслаць электронны ліст без адрасу дастаўкі',
+'user-mail-no-body' => 'Спроба даслаць ліст з пустым або надзвычай кароткім зьместам.',
# Change password dialog
'resetpass' => 'Зьмяніць пароль',
'pageinfo-magic-words' => '{{PLURAL:$1|Магічнае слова|Магічныя словы}} ($1)',
'pageinfo-hidden-categories' => '{{PLURAL:$1|Схаваная катэгорыя|Схаваныя катэгорыі}} ($1)',
'pageinfo-templates' => '{{PLURAL:$1|Шаблён|Шаблёны}} ($1)',
+'pageinfo-transclusions' => 'Выкарыстаньне на {{PLURAL:$1|іншай старонцы|іншых старонках}} ($1)',
'pageinfo-toolboxlink' => 'Зьвесткі пра старонку',
'pageinfo-redirectsto' => 'Перанакіроўвае на',
'pageinfo-redirectsto-info' => 'інфармацыя',
'tog-externaleditor' => 'Eksterny editor ako standard wužywaś (jano za ekspertow, pomina sebje specialne nastajenja na wašom licadle. [//www.mediawiki.org/wiki/Manual:External_editors Dalšne informacije.])',
'tog-externaldiff' => 'Eksterny diff ako standard wužywaś (jano za ekspertow, pomina sebje specialne nastajenja na wašom licadle. [//www.mediawiki.org/wiki/Manual:External_editors Dalšne informacije.])',
'tog-showjumplinks' => 'Wótkaze typa „źi do” zmóžniś',
-'tog-uselivepreview' => 'Live-pśeglěd wužywaś (JavaScript) (eksperimentelnje)',
+'tog-uselivepreview' => 'Live-pśeglěd wužywaś (JavaScript)',
'tog-forceeditsummary' => 'Warnowaś, gaž pśi składowanju zespominanje felujo',
'tog-watchlisthideown' => 'Móje změny na wobglědowańskej lisćinje schowaś',
'tog-watchlisthidebots' => 'Změny awtomatiskich programow (botow) na wobglědowańskej lisćinje schowaś',
'newwindow' => '(se wótcynijo w nowem woknje)',
'cancel' => 'Pśetergnuś',
'moredotdotdot' => 'Wěcej…',
+'morenotlisted' => 'Dalšne njepódane...',
'mypage' => 'Bok',
'mytalk' => 'Diskusija',
'anontalk' => 'Diskusija z toś teju IP',
# E-mail sending
'php-mail-error-unknown' => 'Njeznata zmólka w PHP-funkciji mail()',
'user-mail-no-addy' => 'Jo se wopytało, e-mail bźez e-mailoweje adrese pósłaś',
+'user-mail-no-body' => 'Jo se wopytało, e-mail bźez teksta abo z pśekrotkim tekstom pósłaś',
# Change password dialog
'resetpass' => 'Gronidło změniś',
'pageinfo-magic-words' => '{{PLURAL:$1|Magiske słowo|Magiskej słowje|Magiske słowa|Magiske słowa}} ($1)',
'pageinfo-hidden-categories' => '{{PLURAL:$1|Schowana kategorija|Schowanej kategoriji|Schowane kategorije|Schowane kategorije}} ($1)',
'pageinfo-templates' => '{{PLURAL:$1|Zapśěgnjona pśedłoga|Zapśěgnjonej pśedłoze|Zapśěgnjone pśedłogi|Zapśěgnjone pśedłogi}} ($1)',
+'pageinfo-transclusions' => '{{PLURAL:$1|Bok zapśěgnjony|Boka zapśěgnjonej|Boki zapśěgnjone}} do ($1)',
'pageinfo-toolboxlink' => 'Informacije wó boku',
'pageinfo-redirectsto' => 'Pósrědnja dalej k',
'pageinfo-redirectsto-info' => 'Info',
'category-empty' => "''މި ޤިސްމުގައި އެއްވެސް ސަފްހާ އެއް އަދި އެއްވެސް ފައިލެއް ނުހިމެނެއެވެ.''",
'hidden-categories' => '{{PLURAL:$1|ފޮރުވިފައިވާ ޤިސްމު|ފޮރުވިފައިވާ ޤިސްމުތައް}}',
'hidden-category-category' => 'ފޮރުވިފައިވާ ޤިސްމުތައް',
+'category-subcat-count-limited' => 'މި ޤިސްމުގައި ހިމެނެނީ {{PLURAL:$1|ކުދިޤިސްމެވެ|$1 ކުދިޤިސްމުތަކެވެ}}.',
'about' => 'ތަޢާރަފު',
'article' => 'ފިހުރިސްތު ޞަފްޙާ',
'create-this-page' => 'މި ޞަފްޙާ ފަށްޓަވާ',
'delete' => 'ފޮހެލައްވާ',
'deletethispage' => 'މި ޞަފްޙާ ފޮހެލައްވާ',
+'viewdeleted_short' => '{{PLURAL:$1|ފޮހެލެވިފައިވާ އެއް އުނިއިތުރު|ފޮހެލެވިފައިވާ $1 އުނިއިތުރު}} ބައްލަވާ',
'protect' => 'ދިފާއުކުރައްވާ',
'protect_change' => 'ބަދަލު ގެންނަވާ',
'protectthispage' => 'މި ޞަފްޙާ ދިފާއުކުރައްވާ',
'italic_sample' => 'ކަތި އިބާރާތް',
'italic_tip' => 'ކަތި އިބާރާތް',
'headline_sample' => 'ސުރުހީގެ އިބާރާތް',
+'media_tip' => 'ފައިލު ފާލަން',
'sig_tip' => 'ތިޔަބޭފުޅާގެ ސޮއި، ތާރީޚް ތަތްގަނޑާއެކު',
# Edit pages
'newarticle' => '(އައު)',
'noarticletext' => 'މި ޞަފްޙާގައި އެއްވެސް ލިޔުމެއް ނުވެއެވެ. ތިޔަބޭފުޅާއަށް މި ނަން [[Special:Search/{{PAGENAME}}|އެހެން ޞަފްޙާތަކުން ހޯއްދެވިދާނެއެވެ]]. ނުވަތަ <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} މިއާ ގުޅޭ ލޮގްތައް ހޯއްދެވިދާނެއެވެ].
[{{fullurl:{{FULLPAGENAME}}|action=edit}} ނުވަތަ މި ޞަފްޙާއަށް އުނިއިތުރު ގެނެވިދާނެއެވެ].</span>.',
+'previewnote' => "'''މިއީ ހަމައެކަނި ނަމޫނާ އެކެވެ.'''
+އަދި ތިބޭފުޅާގެ ބަދަލުތައް ރައްކާނުކުރެވެއެވެ!",
'editing' => '$1 އަށް އުނިއިތުރު ގެންނަނީ',
'creating' => '$1 ފަށްޓަވަނީ',
'editingsection' => '$1ގެ ބަޔަކަށް އުނިއިތުރު ގެންނަނީ',
'templatesused' => 'މި ޞަފްޙާ ގައި ބޭނުން ކުރެވިފައިވާ {{PLURAL:$1|ފަންވަތް|ފަންވަތްތައް}}:',
'template-protected' => '(ދިފާޢުކުރެވިފައި)',
'template-semiprotected' => '(ބައެއް ދިފާޢުކުރެވިފައި)',
+'recreate-moveddeleted-warn' => "'''ސަމާލުކަމަށް: ތިޔަ ފަށްޓަވަން އުޅުއްވަނީ ކުރީގައި ފޮހެލެވިފައިވާ ޞަފްޙާއެކެވެ.'''
+
+މި ޞަފްޙާ ކުރިއަށް ގެންދެވުމަށް ރަނގަޅުތޯ އަދި އެއްފަހަރު ވިސްނަވާލައްވާށެވެ.
+ފޮހެލެވުނު އަދި ބަދަލުކުރެވުނު ލޮގް ތިރީގައި ވަނީއެވެ :",
+'moveddeleted-notice' => 'މި ޞަފްޙާ ވަނީ ފޮހެލެވިފައެވެ.
+ފޮހެލުމުގެ އަދި ނަން ބަދަލުކުރުމުގެ ލޮގް ތިރީގައިވަނީއެވެ.',
# History pages
'currentrev' => 'އެންމެފަހުން ގެނެވުނު ބަދަލު',
'previousrevision' => '→ ކުރީގެ ނުސްހާ',
'nextrevision' => 'ފަހުގެ ނުސްހާ ←',
'next' => 'ކުރިޔަށް',
+'histfirst' => 'އެންމެ ކުރީގެ',
+'histlast' => 'އެންމެ ފަހުގެ',
# Revision deletion
'revdel-restore-deleted' => 'ފޮހެލެވިފައިވާ ނުސްހާތައް',
+'revdel-restore-visible' => 'ފާޅު ނުސްހާތައް',
# Diffs
'lineno' => 'ފޮޅުވަތް $1:',
# Recent changes
'recentchanges' => 'އެންމެ ފަހުގެ ބަދަލުތައް',
'recentchanges-summary' => 'މި ވިކިޕީޑިއާ އަށް ގެނެވިފައިވާ އެންމެ ފަހުގެ ބަދަލުތައް މި ޞަފްހާ އިން ބައްލަވާ!',
+'recentchanges-label-newpage' => 'މި އުނިއިތުރުން އާ ޞަފްޙާއެއް ފަށައިގަނެވުނެވެ.',
+'recentchanges-label-minor' => 'މިއީ ކުޑަކުޑަ އުނިއިތުރެކެވެ.',
+'recentchanges-label-bot' => 'މި އުނިއިތުރު ގެނައީ ބޮޓެކެވެ.',
'diff' => 'ފަރަގު',
+'hist' => 'ތާރީޚް',
'hide' => 'ފޮރުވާ',
'show' => 'ދައްކަވާ',
'filehist-user' => 'މެމްބަރު',
'filehist-comment' => 'ޚިޔާލު',
'imagelinks' => 'ފާލަންތައް',
+'sharedupload-desc-here' => 'މި ފައިލަކީ $1ގެ ފައިލެކެވެ. އަދި އެހެން މަޝްރޫޢުތަކުގައި ބޭނުން ކުރެވިފައި ހުރެދާނެއެވެ.
+މި ފައިލުގެ ތަފްސީލް [$2 ފައިލު ތަފްސީލް ޞަފްޙާއިން] ތިރީގައިވަނީއެވެ.',
# Random page
'randompage' => 'ކޮންމެވެސް ޞަފްޙާއެއް',
'deletecomment' => 'ސަބަބު',
# Rollback
+'rollbacklink' => 'ކުރީގެ ނުސްހާ އަކަށް ބަދަލުކުރައްވާ',
'cantrollback' => 'އުނިއިތުރު އިއާދައެއް ނުކުރެވޭނެ؛ އެހެނީ އެންމެ ފަހު އުނިއިތުރުގައި ހިއްސާވި ފަރާތަކީ މިޞަފްޙާގެ ހަމައެކަނި މުއައްލިފެވެ.',
# Protect
'contributions' => 'މެންބަރު ގެ ހިއްސާ',
'mycontris' => 'މަގޭ ހިއްސާ',
+'sp-contributions-talk' => 'ވާހަކަ',
'sp-contributions-userrights' => 'މެންބަރުގެ ހައްގުތަކުގެ އިންތިޒާމް',
+'sp-contributions-search' => 'ހިއްސާތަށް ހޯއްދަވާ',
# What links here
'whatlinkshere' => 'މިއާ ގުޅެނީ ކޮންއެއްޗެއް',
'ipblocklist' => 'ފިޔަވަޅު އެޅިފައިވާ މެމްބަރުން',
'expiringblock' => 'މުއްދަތު ހަމަވާނީ $1 $2',
'blocklink' => 'ފިޔަވަޅުއަޅުއްވާ',
+'unblocklink' => 'ފިޔަވަޅުއެޅުން ބަދަލުކުރައްވާ',
'contribslink' => 'ޙިއްޞާ',
'proxyblocksuccess' => 'ފުރިހަމަވެއްޖެ.',
'tooltip-ca-viewsource' => 'މި ޞަފްޙާވަނީ ދިފާޢުކުރެވިފައެވެ.
މި ޞަފްޙާގެ މަސްދަރު ތިބޭފުޅާއަށް ބައްލަވާލެއްވޭނެއެވެ.',
'tooltip-ca-history' => 'މި ޞަފްޙާގެ ކުރީގެ ނުސްހާތައް',
+'tooltip-ca-protect' => 'މި ޞަފްޙާ ދިފާޢުކުރައްވާ',
'tooltip-ca-delete' => 'މި ޞަފްޙާ ފޮހެލައްވާ',
'tooltip-ca-move' => 'މި ޞަފްހާގެ ނަން/ތަން ބަދަލުކުރައްވާ',
'tooltip-ca-watch' => 'މި ޞަފްޙާއަށް ނަޒަރު ބަހައްޓަވާ',
+'tooltip-ca-unwatch' => 'މަގޭ ނަޒަރުން މި ޞަފްޙާ ދުރުކޮށްލައްވާ',
'tooltip-search' => '{{SITENAME}}އިން ހޯއްދަވާ',
'tooltip-search-fulltext' => 'މި ބަސް ޞަފްޙާތަކުން ހޯއްދަވާ',
'tooltip-p-logo' => 'މައި ޞަފްޙާއަށް ވަޑައިގަންނަވާ',
'tooltip-ca-nstab-main' => 'މަޢުލޫމާތު ޞަފްޙާ ބައްލަވާ',
'tooltip-ca-nstab-user' => 'މެމްބަރު ޞަފްޙާ ބައްލަވާ',
'tooltip-ca-nstab-special' => 'މިއީ ޚާއްސަ ޞަފްޙާއެކެވެ. މި ޞަފްޙާއަށް އުނިއިތުރު ނުގެނެވޭނެއެވެ.',
+'tooltip-ca-nstab-project' => 'މަޝްރޫޢު ޞަފްޙާ ބައްލަވާ',
'tooltip-ca-nstab-image' => 'ފައިލު ޞަފްޙާ ބައްލަވާ',
'tooltip-ca-nstab-template' => 'ފަންވަތް ބައްލަވާ',
'tooltip-ca-nstab-category' => 'ޤިސްމު ޞަފްޙާ ބައްލަވާ',
'tooltip-save' => 'ބަދަލުތައް ރައްކާކުރައްވާ',
'tooltip-preview' => 'ބަދަލުތައް ދައްކަވާ، ރައްކާކުރެއްވުމުގެ ކުރިން މި ބޭނުންކުރައްވާ!',
+'tooltip-rollback' => '"ކުރީގެ ނުސްހާ އަކަށް ބަދަލުކުރައްވާ" އިން މި ޞަފްޙާއަށް އެންމެ ފަހުން އުނިއިތުރު ގެންނެވި މެމްބަރުގެ އުނިއިތުރު(އުނިއިތުރުތައް) ފޮހެލެވޭނެއެވެ.',
'tooltip-summary' => 'ކުރު ޚުލާސާއެއް ލިޔުއްވާ',
# Info page
'tog-externaleditor' => 'Käytä ulkoista tekstieditoria oletuksena. Vain kokeneille käyttäjille, vaatii selaimen asetusten muuttamista. (<span class="plainlinks">[//www.mediawiki.org/wiki/Manual:External_editors Ohje]</span>)',
'tog-externaldiff' => 'Käytä oletuksena ulkoista työkalua sivun eri versioiden välisten erojen tarkasteluun. Vain kokeneille käyttäjille, vaatii selaimen asetusten muuttamista. (<span class="plainlinks">[//www.mediawiki.org/wiki/Manual:External_editors Ohje]</span>)',
'tog-showjumplinks' => 'Lisää loikkaa-käytettävyyslinkit sivun alkuun',
-'tog-uselivepreview' => 'Käytä pikaesikatselua (JavaScript) (kokeellinen)',
+'tog-uselivepreview' => 'Käytä pikaesikatselua (vaatii JavaScriptin)',
'tog-forceeditsummary' => 'Huomauta, jos yhteenvetoa ei ole annettu',
'tog-watchlisthideown' => 'Piilota omat muokkaukset',
'tog-watchlisthidebots' => 'Piilota bottien muokkaukset',
'template-protected' => '(מוגנת)',
'template-semiprotected' => '(מוגנת חלקית)',
'hiddencategories' => 'דף זה כלול ב{{PLURAL:$1|קטגוריה מוסתרת אחת|־$1 קטגוריות מוסתרות}}:',
-'edittools' => '<!-- הטקסט הנכתב כאן יוצג מתחת לטפסי עריכת דפים והעלאת קבצים, ולפיכך ניתן לכתוב להציג בו תווים קשים לכתיבה, קטעים מוכנים של טקסט ועוד. -->',
+'edittools' => '<!-- ×\94×\98קס×\98 ×\94× ×\9bת×\91 ×\9b×\90×\9f ×\99×\95צ×\92 ×\9eת×\97ת ×\9c×\98×\95פס×\99 ער×\99×\9bת ×\93פ×\99×\9d ×\95×\94×¢×\9c×\90ת ק×\91צ×\99×\9d, ×\95×\9cפ×\99×\9b×\9a × ×\99ת×\9f ×\9c×\9bת×\95×\91 ×\9c×\94צ×\99×\92 ×\91×\95 ת×\95×\95×\99×\9d קש×\99×\9d ×\9c×\9bת×\99×\91×\94, ק×\98×¢×\99×\9d ×\9e×\95×\9b× ×\99×\9d ש×\9c ×\98קס×\98 ×\95×¢×\95×\93. -->',
'nocreatetext' => 'אתר זה מגביל את האפשרות ליצור דפים חדשים. באפשרותכם לחזור אחורה ולערוך דף קיים, או [[Special:UserLogin|להיכנס לחשבון]].',
'nocreate-loggedin' => 'אינכם מורשים ליצור דפים חדשים.',
'sectioneditnotsupported-title' => 'עריכת פסקאות אינה נתמכת',
'sp-newimages-showfrom' => 'הצגת קבצים חדשים החל מ־$2, $1',
# Video information, used by Language::formatTimePeriod() to format lengths in the above messages
+'seconds-abbrev' => '{{PLURAL:$1|שנייה|$1 שניות}}',
+'minutes-abbrev' => "{{PLURAL:$1|דקה|$1 דק'}}",
+'hours-abbrev' => '{{PLURAL:$1|שעה|שעתיים|$1 שעות}}',
+'days-abbrev' => '{{PLURAL:$1|יום|יומיים|$1 ימים}}',
'seconds' => '{{PLURAL:$1|שנייה|$1 שניות}}',
'minutes' => '{{PLURAL:$1|דקה|$1 דקות}}',
'hours' => '{{PLURAL:$1|שעה|שעתיים|$1 שעות}}',
'tog-externaleditor' => 'Eksterny editor jako standard wužiwać (jenož za ekspertow, žada sej specialne nastajenja na wašim ličaku. [//www.mediawiki.org/wiki/Manual:External_editors Dalše informacije.])',
'tog-externaldiff' => 'Eksterny diff-program jako standard wužiwać (jenož za ekspertow, žada sej specialne nastajenja na wašim ličaku. [//www.mediawiki.org/wiki/Manual:External_editors Dalše informacije.])',
'tog-showjumplinks' => 'Wotkazy typa „dźi do” zmóžnić',
-'tog-uselivepreview' => 'Live-přehlad wužiwać (wužaduje sej JavaScript) (eksperimentalny)',
+'tog-uselivepreview' => 'Live-přehlad wužiwać (wužaduje sej JavaScript)',
'tog-forceeditsummary' => 'Mje skedźbnić, jeli zabudu zjeće',
'tog-watchlisthideown' => 'Moje změny we wobkedźbowankach schować',
'tog-watchlisthidebots' => 'Změny awtomatiskich programow (botow) we wobkedźbowankach schować',
'newwindow' => '(wočinja so w nowym woknje)',
'cancel' => 'Přetorhnyć',
'moredotdotdot' => 'Wjace…',
+'morenotlisted' => 'Dalše njepodate...',
'mypage' => 'Strona',
'mytalk' => 'Diskusija',
'anontalk' => 'Diskusijna strona tuteje IP.adresy',
# E-mail sending
'php-mail-error-unknown' => 'Njeznaty zmylk w PHP-funkciji mail()',
'user-mail-no-addy' => 'Je so spytało e-mejl bjez e-mejloweje adresy słać.',
+'user-mail-no-body' => 'Je so spytało, e-mejl bjez teksta abo z překrótkim tekstom pósłać',
# Change password dialog
'resetpass' => 'Hesło změnić',
'pageinfo-magic-words' => '{{PLURAL:$1|Magiske słowo|Magiskej słowje|Magiske słowa|Magiske słowa}} ($1)',
'pageinfo-hidden-categories' => '{{PLURAL:$1|Schowana kategorija|Schowanej kategoriji|Schowane kategorije|Schowane kategorije}} ($1)',
'pageinfo-templates' => '{{PLURAL:$1|Zapřijata předłoha|Zapřijatej předłoze|Zapřijate předłohi|Zapřijate předłohi}} ($1)',
+'pageinfo-transclusions' => '{{PLURAL:$1|Strona zapřijata|Stronje zapřijatej|Strony zapřijate}} do ($1)',
'pageinfo-toolboxlink' => 'Informacije wo stronje',
'pageinfo-redirectsto' => 'Sposrědkuje k',
'pageinfo-redirectsto-info' => 'Info',
'whatlinkshere-page' => 'ページ:',
'linkshere' => "以下のページが、'''[[:$1]]'''にリンクしています:",
'nolinkshere' => "'''[[:$1]]'''にリンクしているページはありません。",
-'nolinkshere-ns' => "選択された名前空間中で、'''[[:$1]]'''にリンクしているページはありません。",
+'nolinkshere-ns' => "指定した名前空間内に、'''[[:$1]]'''にリンクしているページはありません。",
'isredirect' => '転送ページ',
'istemplate' => '参照読み込み',
'isimage' => 'ファイルへのリンク',
'dberr-again' => '数分間待った後、もう一度読み込んでください。',
'dberr-info' => '(データベースサーバー $1 に接続できませんでした)',
'dberr-usegoogle' => '元に戻るまで、Googleを利用して検索できます。',
-'dberr-outofdate' => 'それらが収集した内容は古い可能性があることに注意してください。',
+'dberr-outofdate' => '収集された内容は古い可能性があることに注意してください。',
'dberr-cachederror' => 'これは要求されたページをキャッシュした複製であり、古くなっている可能性があります。',
# HTML forms
'tog-externaleditor' => 'സ്വതേ ബാഹ്യ എഡിറ്റർ ഉപയോഗിക്കുക (വിദഗ്ദ്ധ ഉപയോക്താക്കൾക്കു മാത്രം, താങ്കളുടെ കമ്പ്യൂട്ടറിൽ പ്രത്യേക സജ്ജീകരണങ്ങൾ ആവശ്യമാണ്. [//www.mediawiki.org/wiki/Manual:External_editors കൂടുതൽ വിവരങ്ങൾ.])',
'tog-externaldiff' => 'വ്യത്യാസം അറിയാൻ സ്വതേ ബാഹ്യ ഉപകരണങ്ങൾ ഉപയോഗിക്കുക (വിദഗ്ദ്ധ ഉപയോക്താക്കൾക്കു മാത്രം, താങ്കളുടെ കമ്പ്യൂട്ടറിൽ പ്രത്യേക സജ്ജീകരണങ്ങൾ ആവശ്യമാണ്. [//www.mediawiki.org/wiki/Manual:External_editors കൂടുതൽ വിവരങ്ങൾ.])',
'tog-showjumplinks' => '"പോവുക" ഗമ്യത കണ്ണികൾ പ്രാപ്തമാക്കുക',
-'tog-uselivepreview' => 'തത്സമയ പ്രിവ്യൂ ഉപയോഗപ്പെടുത്തുക (ജാവാസ്ക്രിപ്റ്റ്) (പരീക്ഷണാടിസ്ഥാനത്തിലുള്ളത്)',
+'tog-uselivepreview' => 'തത്സമയ പ്രിവ്യൂ ഉപയോഗപ്പെടുത്തുക (ജാവാസ്ക്രിപ്റ്റ് ആവശ്യമാണ്)',
'tog-forceeditsummary' => 'തിരുത്തലുകളുടെ ചുരുക്കം നൽകിയില്ലെങ്കിൽ എന്നെ ഓർമ്മിപ്പിക്കുക',
'tog-watchlisthideown' => 'ഞാൻ ശ്രദ്ധിക്കുന്ന താളുകളുടെ പട്ടികയിൽനിന്ന് എന്റെ തിരുത്തലുകൾ മറയ്ക്കുക',
'tog-watchlisthidebots' => 'ഞാൻ ശ്രദ്ധിക്കുന്ന താളുകളുടെ പട്ടികയിൽനിന്ന് യന്ത്രങ്ങൾ വരുത്തിയ തിരുത്തലുകൾ മറയ്ക്കുക',
'newwindow' => '(പുതിയ ജാലകത്തിൽ തുറന്നു വരും)',
'cancel' => 'റദ്ദാക്കുക',
'moredotdotdot' => 'കൂടുതൽ...',
+'morenotlisted' => 'ബാക്കി പട്ടികയിൽ ഉൾപ്പെടുത്തിയിട്ടില്ല...',
'mypage' => 'താൾ',
'mytalk' => 'സംവാദത്താൾ',
'anontalk' => 'ഈ ഐ.പി.യുടെ സംവാദം താൾ',
# E-mail sending
'php-mail-error-unknown' => 'പി.എച്ച്.പി.യുടെ main() ഫങ്ഷനിൽ അപരിചിതമായ പിഴവ്',
'user-mail-no-addy' => 'ഇമെയിൽ വിലാസം ഇല്ലാതെയാണ് ഇമെയിൽ അയയ്ക്കാൻ ശ്രമിച്ചത്',
+'user-mail-no-body' => 'ശൂന്യമായതോ അസാമാന്യമായി ചെറുതോ ആയ ഉള്ളടക്കമുള്ള ഇമെയിൽ അയയ്ക്കാൻ ശ്രമിച്ചു.',
# Change password dialog
'resetpass' => 'രഹസ്യവാക്ക് മാറ്റുക',
'prot_1movedto2' => '[[$1]] എന്ന താളിന്റെ പേർ [[$2]] എന്നാക്കിയിരിക്കുന്നു',
'protect-badnamespace-title' => 'സംരക്ഷിക്കാനാവാത്ത നാമമേഖല',
'protect-badnamespace-text' => 'ഈ നാമമേഖലയിലെ താളുകൾ സംരക്ഷിക്കാനാവില്ല.',
+'protect-norestrictiontypes-text' => 'പരിധി നിർണ്ണയിക്കാനുള്ള വിധം ഇല്ലാത്തതിനാൽ ഈ താൾ സംരക്ഷിക്കാനാവില്ല.',
+'protect-norestrictiontypes-title' => 'സംരക്ഷിക്കാനാവാത്ത താൾ',
'protect-legend' => 'സംരക്ഷണം സ്ഥിരീകരിക്കുക',
'protectcomment' => 'കാരണം:',
'protectexpiry' => 'സംരക്ഷണ കാലാവധി:',
'pageinfo-protect-cascading' => 'സംരക്ഷണങ്ങൾ ഇവിടെ നിന്ന് നിർഝരിതപ്പെടുത്തുന്നു',
'pageinfo-protect-cascading-yes' => 'അതെ',
'pageinfo-protect-cascading-from' => 'സംരക്ഷണങ്ങൾ നിർഝരിതപ്പെടുത്തുന്നത്',
+'pageinfo-category-info' => 'വർഗ്ഗത്തിന്റെ വിവരങ്ങൾ',
+'pageinfo-category-pages' => 'താളുകളുടെ എണ്ണം',
+'pageinfo-category-subcats' => 'ഉപവർഗ്ഗങ്ങളുടെ എണ്ണം',
+'pageinfo-category-files' => 'പ്രമാണങ്ങളുടെ എണ്ണം',
# Skin names
'skinname-standard' => 'സാർവത്രികം',
'minutes' => '{{PLURAL:$1|ഒരു മിനിറ്റ്|$1 മിനിറ്റ്}}',
'hours' => '{{PLURAL:$1|ഒരു മണിക്കൂർ|$1 മണിക്കൂർ}}',
'days' => '{{PLURAL:$1|ഒരു ദിവസം|$1 ദിവസം}}',
+'months' => '{{PLURAL:$1|ഒരു മാസം|$1 മാസം}}',
+'years' => '{{PLURAL:$1|ഒരു വർഷം|$1 വർഷം}}',
'ago' => '$1 മുമ്പ്',
'just-now' => 'ഇപ്പോൾ',
'tog-externaldiff' => 'Bruk eit eksternt skilnadprogram som standard (berre for vidarekomne, krev eit spesielt oppsett på maskina di.
[//www.mediawiki.org/wiki/Manual:External_editors Meir informasjon.])',
'tog-showjumplinks' => 'Slå på «gå til»-lenkjer',
-'tog-uselivepreview' => 'Bruk levande førehandsvising (eksperimentelt JavaScript)',
+'tog-uselivepreview' => 'Bruk levande førehandsvising (krev JavaScript)',
'tog-forceeditsummary' => 'Spør meg når eg ikkje har skrive noko i endringssamandraget',
'tog-watchlisthideown' => 'Gøym endringane mine i overvakingslista',
'tog-watchlisthidebots' => 'Gøym endringar gjorde av robotar i overvakingslista',
'newwindow' => '(vert opna i eit nytt vindauge)',
'cancel' => 'Avbryt',
'moredotdotdot' => 'Meir …',
+'morenotlisted' => 'Meir som ikkje er lista opp …',
'mypage' => 'Sida mi',
'mytalk' => 'Diskusjon',
'anontalk' => 'Diskusjonside for denne IP-adressa',
'prot_1movedto2' => '«[[$1]]» flytt til «[[$2]]»',
'protect-badnamespace-title' => 'Namnerommet kan ikkje vernast',
'protect-badnamespace-text' => 'Sider i dette namnerommet kan ikkje vernast.',
+'protect-norestrictiontypes-text' => 'Sida kan ikkje vernast sidan det ikkje finst tilgjengelege restriksjonstypar.',
+'protect-norestrictiontypes-title' => 'Side som ikkje kan vernast',
'protect-legend' => 'Stadfest vern',
'protectcomment' => 'Grunngjeving:',
'protectexpiry' => 'Endar:',
'pageinfo-protect-cascading' => 'Djupvern byrjar her',
'pageinfo-protect-cascading-yes' => 'Ja',
'pageinfo-protect-cascading-from' => 'Djupvern byrjar i',
+'pageinfo-category-info' => 'Kategoriinformasjon',
+'pageinfo-category-pages' => 'Tal sider',
+'pageinfo-category-subcats' => 'Tal underkategoriar',
+'pageinfo-category-files' => 'Tal filer',
# Skin names
'skinname-standard' => 'Klassisk',
'minutes' => '{{PLURAL:$1|$1 minutt|$1 minutt}}',
'hours' => '{{PLURAL:$1|$1 time|$1 timar}}',
'days' => '{{PLURAL:$1|$1 dag|$1 dagar}}',
+'months' => '{{PLURAL:$1|éin månad|$1 månader}}',
+'years' => '{{PLURAL:$1|éitt år|$1 år}}',
'ago' => '$1 sidan',
'just-now' => 'akkurat no',
'august' => 'Auguschd',
'september' => 'Sebdember',
'october' => 'Ogdower',
-'november' => 'November',
+'november' => 'Nowember',
'december' => 'Dezember',
'january-gen' => 'Jänner',
'february-gen' => 'Fewwer',
'august-gen' => 'Auguschd',
'september-gen' => 'Sebdember',
'october-gen' => 'Ogdower',
-'november-gen' => 'November',
+'november-gen' => 'Nowember',
'december-gen' => 'Dezember',
'jan' => 'Jän',
'feb' => 'Few',
'subcategories' => 'Unnerkadegorie',
'category-media-header' => 'Medie in de Kadegorie „$1“',
'hidden-categories' => '{{PLURAL:$1|Verschdegelde Kadegorie|Verschdegelde Kadegorije}}',
+'hidden-category-category' => 'Verschdegelde Kadegorije',
'category-subcat-count' => '{{PLURAL:$2|Die Kategorie hot die Unnerkategorie:|{{PLURAL:$1|Die Unnerkategori isch eni vun insgsamt $2 Unnerkategorie in derre Kategorie:|S werre $1 vun insgsamt $2 Unnerkategorie in derre Kategorie aagezeicht:}}}}',
'category-article-count' => "{{PLURAL:$2|In derre Kadegorie hot's numme die Said.|Die {{PLURAL:$1|Said|$1 Saide}} gebbt's in derre Kadegorie, vun insgsamt $2.}}",
'listingcontinuesabbrev' => '(Forts.)',
'about' => 'Iwwer',
'newwindow' => '(werd im e naie Fenschter uffgmacht)',
'cancel' => 'Abbreche',
+'mypage' => 'Said',
'mytalk' => 'Dischbediere',
'navigation' => 'Nawigadzion',
'editthispage' => 'Die Said bearwaide',
'delete' => 'Lesche',
'undelete_short' => '{{PLURAL:$1|ä Ännerung|$1 Ännerunge}} widderherschdelle',
-'protect' => 'schitze',
+'protect' => 'schidze',
'protect_change' => 'ännere',
'protectthispage' => 'Die Said schidze',
'unprotect' => 'Saideschudz änare',
'yourtext' => 'Doin Tegschd',
'storedversion' => 'Gschbaischerdi Version',
'yourdiff' => 'Unaschied',
-'copyrightwarning' => "Bitte gebb acht, dass alle Baidräch zu {{SITENAME}} unner $2 vereffentlicht werre (guck $1 fer mehr Details).
-Wenn du nit willhsct, dass deswu du gschriwwe hoscht, gänneret un kopiert werre kann, dann duu s do nit naischraiwe.<br />
-du gebbscht do au zu, dass Du des selwerscht gschriwwe hoscht orrer vun ere effentliche, fraie Quell ('''public domain''')orrer vun ere ähnliche fraie Quell her hoscht.
-'''SCHRAIB DO NIX NAI, WAS URHEWERRECHTKLICH GSCHITZT ISCH!'''",
+'copyrightwarning' => "Bidde gebb achd, dass alle Baidräch zu {{SITENAME}} unner $2 vereffentlischd werre (guck $1 fer mehr Details).
+Wenn du nit willschd, dass deswu du gschriwwe hoschd, gänneret un kopierd werre kann, dann duu s do nit naischraiwe.<br />
+du gebbschd do au zu, dass Du des selwerschd gschriwwe hoschd orrer vun ere effendliche, fraie Quell ('''public domain''') orrer vun ere ähnliche fraie Quell her hoschd.
+'''SCHRAIB DO NIX NAI, WAS URHEWERRECHDLICH GSCHIZD ISCH!'''",
'templatesused' => '{{PLURAL:$1|Vorlach wu uff derre Said gebraucht werd|Vorlache wu uff derre Said gebraucht werre}}:',
'templatesusedpreview' => '{{PLURAL:$1|Vorlach wu in derre Vorschau gebraucht werd|Vorlache wu in derre Vorschau gebraucht werre}}:',
-'template-protected' => '(gschitzt)',
-'template-semiprotected' => '(halb-gschitzt)',
+'template-protected' => '(gschizd)',
+'template-semiprotected' => '(halb-gschizd)',
'hiddencategories' => 'Die Said ghert zu {{PLURAL:$1|1 versteckelte Kategorie|$1 versteckelte Kategorie}}:',
'permissionserrorstext-withaction' => 'Du därfscht nid $2, aus {{PLURAL:$1|dem Grund|denne Grind}}:',
'moveddeleted-notice' => 'Die Said isch gleschd worre.
# Protect
'protectlogpage' => 'Saideschutz-Logbuch',
-'protectedarticle' => 'hot "[[$1]]" gschitzt',
+'protectedarticle' => 'hot "[[$1]]" gschizd',
'modifiedarticleprotection' => 'hot de Schutzstatus vun "[[$1]]" gännert',
'protectcomment' => 'Grund:',
'protectexpiry' => 'Bis:',
'protect-text' => "Du kannscht de Schutzstatus vun de Said '''$1''' aagucke un ännere.",
'protect-locked-access' => "Dai Benutzerkonto hot ken Recht zum de Schutzstatus vun ener Said ze ännere.
Do hot s di aktuelle Aistellunge vun de Said '''$1''':",
-'protect-cascadeon' => 'Die Said isch gschitzt, wail se {{PLURAL:$1|zu derre Said ghert|zu denne Saide ghert}}, wu e Kaskadesperrung gelt.
+'protect-cascadeon' => 'Die Said isch gschizd, wail se {{PLURAL:$1|zu derre Said ghert|zu denne Saide ghert}}, wu e Kaskadesperrung gelt.
Der Schutzstatus vun derre Said kannscht ännere, awwer des hot kää Aifluss uff d Kaskadesperrung.',
'protect-default' => 'Alle Benutzer erlääwe',
'protect-fallback' => '«$1»-Berechdichung nedich',
'tooltip-ca-edit' => 'Du kannschd die Said bearwaide.
Bidde nemmde Vorschau-Knobb vorm Schbaischere',
'tooltip-ca-addsection' => 'E naie Abschnitt aaleche',
-'tooltip-ca-viewsource' => 'Die Said isch gschitzt.
-Du kannscht awwer de Quelltext aagucke',
+'tooltip-ca-viewsource' => 'Die Said isch gschizd.
+Du kannscht awwer de Quelltegschd aagucke',
'tooltip-ca-history' => 'Ledschde Versione vun derre Said',
-'tooltip-ca-protect' => 'Die Said schitze',
+'tooltip-ca-protect' => 'Die Said schidze',
'tooltip-ca-delete' => 'Die Said lesche',
'tooltip-ca-move' => 'Die Said verschiewe',
'tooltip-ca-watch' => 'Die Said zu Dainere Beowachdungslischt zufieche',
Për piasì, ch'as butà an comunicassion con n'[[Special:ListUsers/sysop|aministrator]].",
'upload-misc-error' => "Eror nen identificà antramentr ch'as cariava",
'upload-misc-error-text' => "A l'é staie n'eror nen identificà dëmentrè ch'as cariava chèich-còs.
-Për piasì, ch'a varda che soa anliura a sia bon-a e che a l'arsponda e peuj ch'a preuva torna.
+Për piasì, ch'a varda che soa anliura a sia bon-a e che a rësponda e peuj ch'a preuva torna.
Se a-i riva sossì n'àotra vira, ch'as buta an comunicassion con n'[[Special:ListUsers/sysop|aministrator]].",
'upload-too-many-redirects' => "L'adrëssa dl'aragnà a l'avìa tròpe ridiression",
'upload-unknown-size' => 'Dimension pa conossùa',
'http-bad-status' => "A l'é staje un problema durant l'arcesta HTTP: $1 $2",
# Some likely curl errors. More could be added from <http://curl.haxx.se/libcurl/c/libcurl-errors.html>
-'upload-curl-error6' => "L'anliura a l'arspond pa",
+'upload-curl-error6' => "L'anliura a rëspond pa",
'upload-curl-error6-text' => "L'anliura che a l'ha butà a marcia pa. Për piasì, ch'a contròla che st'anliura a sia scrita giusta e che ël sit a marcia.",
'upload-curl-error28' => "A l'é finìje ël temp ch'as peul dovresse për carié",
-'upload-curl-error28-text' => "Ël sit a-i buta tròp temp a arsponde. Për piasì, ch'a contròla che a l'é an pé, ch'a speta na minuta e peuj che a torna a prové. A peul esse che a-j ven-a a taj serne un moment che ës sit a sia nen tant carià ëd tràfich.",
+'upload-curl-error28-text' => "Ël sit a-i buta tròp temp a rësponde. Për piasì, ch'a contròla che a l'é an pé, ch'a speta na minuta e peuj che a torna a prové. A peul esse che a-j ven-a a taj serne un moment che ës sit a sia nen tant carià ëd tràfich.",
'license' => 'Licensa:',
'license-header' => 'Licensa',
# Friendlier slave lag warnings
'lag-warn-normal' => 'Le modìfiche pì neuve ëd $1 {{PLURAL:$1|second}} a podrìo nen ess-ie ant sta lista-sì.',
-'lag-warn-high' => "Për via che la màchina serventa a tarda a dene d'arspòsta, le modìfiche fàite men che $1 {{PLURAL:$1|second}} fa
-a podrìo ëdcò nen ess-ie ant sta lista-sì.",
+'lag-warn-high' => "Për via che la màchina serventa a tarda a dene 'd rispòste, le modìfiche fàite men che $1 {{PLURAL:$1|second}} fa a podrìo ëdcò nen ess-ie ant sta lista-sì.",
# Watchlist editor
'watchlistedit-numitems' => "A l'é antramentr ch'a ten sot-euj {{PLURAL:$1|1 tìtol|$1 tìtoj}}, nen contand le pàgine ëd discussion.",
'search-interwiki-caption' => 'Used in [[Special:Search]], when showing search results from other wikis.',
'search-interwiki-default' => '* $1 is the hostname of the remote wiki from where the additional results listed below are returned',
'search-interwiki-more' => '{{Identical|More}}',
-'search-relatedarticle' => '{{Identical|Related}}
-
-This is a search result (and I guess search engine) dependent messages. I do not know how to trigger the feature. The message is displayed if the search result contains information that related pages can also be provided from the search engine. I assume this is "More Like This" functionality. Microsoft glossary defines MLT as "A way to refine search by identifying the right set of documents and then locating similar documents. This allows the searcher to control the direction of the search and focus on the most fruitful lines of inquiry."[http://www.microsoft.com/enterprisesearch/en/us/search-glossary.aspx]',
+'search-relatedarticle' => 'This is a search result (and I guess search engine) dependent messages. I do not know how to trigger the feature. The message is displayed if the search result contains information that related pages can also be provided from the search engine. I assume this is "More Like This" functionality. Microsoft glossary defines MLT as "A way to refine search by identifying the right set of documents and then locating similar documents. This allows the searcher to control the direction of the search and focus on the most fruitful lines of inquiry."[http://www.microsoft.com/enterprisesearch/en/us/search-glossary.aspx]
+{{Identical|Related}}',
'mwsuggest-disable' => "The text of an option on the 'search options' tab of a user's Preferences.",
'searcheverything-enable' => 'Used in [[Special:Preferences]], tab “Search”.',
-'searchrelated' => '{{Identical|Related}}
-
-This is a search result (and I guess search engine) dependent messages. I do not know how to trigger the feature. The message is displayed if the search result contains information that related pages can also be provided from the search engine. I assume this is "More Like This" functionality. Microsoft glossary defines MLT as "A way to refine search by identifying the right set of documents and then locating similar documents. This allows the searcher to control the direction of the search and focus on the most fruitful lines of inquiry."[http://www.microsoft.com/enterprisesearch/en/us/search-glossary.aspx]',
+'searchrelated' => 'This is a search result (and I guess search engine) dependent messages. I do not know how to trigger the feature. The message is displayed if the search result contains information that related pages can also be provided from the search engine. I assume this is "More Like This" functionality. Microsoft glossary defines MLT as "A way to refine search by identifying the right set of documents and then locating similar documents. This allows the searcher to control the direction of the search and focus on the most fruitful lines of inquiry."[http://www.microsoft.com/enterprisesearch/en/us/search-glossary.aspx]
+{{Identical|Related}}',
'searchall' => '{{Identical|All}}',
'showingresults' => 'This message is used on some special pages such as [[Special:WantedCategories]]. Parameters:
*$1 is the total number of results in the batch shown.
'powersearch-togglenone' => '"None" refers to namespaces. It is used in Advanced search: http://translatewiki.net/w/i.php?title=Special:Search&advanced=1
{{Identical|None}}',
'search-external' => 'Legend of the fieldset for the input form when the internal search is disabled. Inside the fieldset [[MediaWiki:Searchdisabled]] and [[MediaWiki:Googlesearch]] is shown.',
-'searchdisabled' => 'Shown on [[Special:Search]] when the internal search is disabled.',
+'searchdisabled' => '{{doc-singularthey}}
+In this sentence, "their indexes" refers to "Google\'s indexes".
+
+Shown on [[Special:Search]] when the internal search is disabled.',
# Quickbar
'qbsettings' => 'The title of the section in [[Special:Preferences]], only shown when using the skins "Standard/Classic" or "Cologne Blue". The quicbar is the same as the sidebar.',
'timezoneregion-pacific' => 'Used in "Time zone" listbox in [[Special:Preferences#mw-prefsection-datetime|preferences]], "date and time" tab.
{{Related|Timezoneregion}}',
'allowemail' => 'Used in [[Special:Preferences]] > {{int:prefs-personal}} > {{int:email}}.',
-'prefs-searchoptions' => '{{Identical|Search options}}',
+'prefs-searchoptions' => '{{Identical|Search}}',
'prefs-namespaces' => "{{Identical|Namespaces}}
Shown as legend of the second fieldset of the tab 'Search' in [[Special:Preferences]]",
'defaultns' => 'Used in [[Special:Preferences]], tab "Search".',
'editinguser' => 'Appears on [[Special:UserRights]]. Parameters:
* $1 is a username
* $2 are user tool links. Example: "(Talk | contribs | block | send e-mail)".',
-'userrights-editusergroup' => '{{Identical|Edit user groups}}. Parameter:
-* $1 is a username - optional, can be used for GENDER',
+'userrights-editusergroup' => 'Parameter:
+* $1 is a username - optional, can be used for GENDER
+{{Identical|Edit user groups}}',
'saveusergroups' => 'Button text when editing user groups',
'userrights-groupsmember' => 'Used when editing user groups in [[Special:Userrights]]. The message is followed by a list of group names.
'right-minoredit' => '{{doc-right|minoredit}}
The right to use the "This is a minor edit" checkbox. See {{msg|minoredit|pl=yes}} for the message used for that checkbox.',
'right-move' => '{{doc-right|move}}
-The right to move any page that is not protected from moving.',
+The right to move any page that is not protected from moving.
+{{Identical|Move page}}',
'right-move-subpages' => '{{doc-right|move-subpages}}',
'right-move-rootuserpages' => '{{doc-right|move-rootuserpages}}',
'right-movefile' => '{{doc-right|movefile}}',
'right-ipblock-exempt' => '{{doc-right|ipblock-exempt}}
This user automatically bypasses IP blocks, auto-blocks and range blocks - so I presume - but I am uncertain',
'right-proxyunbannable' => '{{doc-right|proxyunbannable}}',
-'right-unblockself' => '{{doc-right|unblockself}}',
+'right-unblockself' => '{{doc-right|unblockself}}
+{{doc-singularthey}}',
'right-protect' => '{{doc-right|protect}}',
'right-editprotected' => '{{doc-right|editprotected}}',
'right-editinterface' => '{{doc-right|editinterface}}',
* {{msg-mw|ipbwatchuser}}
* {{msg-mw|ipb-hardblock}}
{{Identical|Prevent user from sending e-mail}}',
-'ipbenableautoblock' => 'Used as label for checkbox in [[Special:Block]].
+'ipbenableautoblock' => '{{doc-singularthey}}
+Used as label for checkbox in [[Special:Block]].
See also:
* {{msg-mw|ipbemailban}}
'dberr-info' => 'This message does not allow any wiki nor html markup.
* $1 - database server name',
'dberr-usegoogle' => 'This message does not allow any wiki nor html markup.',
-'dberr-outofdate' => "In this sentence, '''their''' indexes refers to '''Google's''' indexes. This message does not allow any wiki nor html markup.",
+'dberr-outofdate' => "{{doc-singularthey}}
+In this sentence, '''their''' indexes refers to '''Google's''' indexes. This message does not allow any wiki nor html markup.",
'dberr-cachederror' => 'Used as error message at the bottom of the page.',
# HTML forms
'newwindow' => '(в новом окне)',
'cancel' => 'Отменить',
'moredotdotdot' => 'Далее…',
+'morenotlisted' => 'Больше ничего нету...',
'mypage' => 'Страница',
'mytalk' => 'Обсуждение',
'anontalk' => 'Обсуждение для этого IP-адреса',
# E-mail sending
'php-mail-error-unknown' => 'Neznana napaka v funkciji PHP mail()',
'user-mail-no-addy' => 'Poskušal poslati e-pošto brez e-poštnega naslova',
+'user-mail-no-body' => 'Poskušali ste poslati e-pošto s prazno ali nerazumno kratko vsebino.',
# Change password dialog
'resetpass' => 'Spremeni geslo',
'tog-externaleditor' => "Використовувати зовнішній редактор за умовчанням (тільки для досвідчених користувачів, вимагає спеціальних налаштувань вашого комп'ютера [//www.mediawiki.org/wiki/Manual:External_editors Детальніше.])",
'tog-externaldiff' => "Використовувати зовнішню програму порівняння версій за умовчанням (тільки для експертів, вимагає спеціальних налаштувань вашого комп'ютера. [//www.mediawiki.org/wiki/Manual:External_editors Детальніше.])",
'tog-showjumplinks' => 'Активізувати допоміжні посилання «перейти до»',
-'tog-uselivepreview' => 'Використовувати швидкий попередній перегляд (JavaScript, експериментально)',
+'tog-uselivepreview' => 'Використовувати швидкий попередній перегляд (JavaScript)',
'tog-forceeditsummary' => 'Попереджати, коли не зазначений короткий опис редагування',
'tog-watchlisthideown' => 'Приховати мої редагування у списку спостереження',
'tog-watchlisthidebots' => 'Приховати редагування ботів у списку спостереження',
'newwindow' => '(відкривається в новому вікні)',
'cancel' => 'Скасувати',
'moredotdotdot' => 'Детальніше…',
+'morenotlisted' => 'Більше немає нічого…',
'mypage' => 'Сторінка',
'mytalk' => 'Обговорення',
'anontalk' => 'Обговорення для цієї IP-адреси',
# E-mail sending
'php-mail-error-unknown' => 'Невідома помилка в PHP-mail() функції',
'user-mail-no-addy' => 'Спроба надсилання електронної пошти без зазначеної адреси електронної пошти.',
+'user-mail-no-body' => 'Спроба надіслати електронного листа із порожнім, або назвичайно коротким вмістом.',
# Change password dialog
'resetpass' => 'Змінити пароль',
'linksearch-pat' => 'Шаблон для пошуку:',
'linksearch-ns' => 'Простір назв:',
'linksearch-ok' => 'Знайти',
-'linksearch-text' => 'Можна використовувати підстановочні символи (шаблони), наприклад, "*.wikipedia.org".
-Необхідний домен принаймні верхнього рівня, наприклад "*.org"<br />
-Підтримувані протоколи: <code>$1</code> (за замовчуванням http:// якщо жоден протокол не вказано)',
+'linksearch-text' => 'Можна використовувати підстановочні символи, наприклад, «*.wikipedia.org».
+Необхідний домен принаймні верхнього рівня, наприклад «*.org».<br />
+{{PLURAL:$2|Підтримуваний протокол|Підтримувані протоколи}}: <code>$1</code> (за замовчуванням http:// якщо жоден протокол не вказано).',
'linksearch-line' => 'Посилання на $1 із $2',
'linksearch-error' => 'Підстановочні знаки можуть використовуватися лише на початку адрес.',
'prot_1movedto2' => '«[[$1]]» перейменована на «[[$2]]»',
'protect-badnamespace-title' => 'Беззахисний простір імен',
'protect-badnamespace-text' => 'Сторінки у просторі імен не можуть бути захищені.',
+'protect-norestrictiontypes-text' => 'Цю сторінку не може бути захищено, бо для неї немає доступних типів обмежень.',
+'protect-norestrictiontypes-title' => 'Сторінка, яку неможливо захистити',
'protect-legend' => 'Підтвердження встановлення захисту',
'protectcomment' => 'Причина:',
'protectexpiry' => 'Закінчується:',
'pageinfo-magic-words' => '{{PLURAL:$1|Магічне слово|Магічні слова}} ($1)',
'pageinfo-hidden-categories' => '{{PLURAL:$1|Прихована категорія|Приховані категорії}} ($1)',
'pageinfo-templates' => 'Включено {{PLURAL:$1|шаблон|шаблонів}} ($1)',
+'pageinfo-transclusions' => 'Включено до ($1) {{PLURAL:$1|сторінки|сторінок}}',
'pageinfo-toolboxlink' => 'Інформація про сторінку',
'pageinfo-redirectsto' => 'Перенаправляє на',
'pageinfo-redirectsto-info' => 'інформація',
'pageinfo-protect-cascading' => 'Звідси розпочинається каскадний захист',
'pageinfo-protect-cascading-yes' => 'Так',
'pageinfo-protect-cascading-from' => 'Каскадний захист починається тут',
+'pageinfo-category-info' => 'Інформація про категорію',
+'pageinfo-category-pages' => 'Число сторінок',
+'pageinfo-category-subcats' => 'Число підкатегорій',
+'pageinfo-category-files' => 'Число файлів',
# Skin names
'skinname-standard' => 'Стандартне',
'cancel' => 'Bekor qilish',
'moredotdotdot' => 'Batafsil...',
'mypage' => 'Sahifa',
-'mytalk' => 'Munozara',
+'mytalk' => 'Munozaram',
'anontalk' => 'Bu IP uchun suhbat',
'navigation' => 'Saytda harakatlanish',
'and' => ' va',
'stub-threshold-disabled' => "O'chirib qo'yilgan",
'recentchangesdays-max' => 'Eng koʻpi $1 kun',
'recentchangescount' => 'Sukut boʻyicha koʻrsatiladigan tahrirlar soni',
+'savedprefs' => 'Sizning moslamalaringiz saqlandi.',
'timezonelegend' => 'Vaqt mintaqangiz:',
'localtime' => 'Mahalliy vaqt:',
'timezoneuseserverdefault' => 'Server moslamalaridan foydalanish ($1)',
# Contributions
'contributions' => '{{GENDER:$1|Foydalanuvchi}} hissasi',
'contributions-title' => '{{GENDER:$1|Foydalanuvchi}} $1 hissasi',
-'mycontris' => 'Hissa',
+'mycontris' => 'Hissam',
'contribsub2' => '$1 uchun ($2)',
'nocontribs' => "Belgilangan shartlarga muvofiq o'zgarishlar topilmadi",
'uctop' => '(oxirgi)',
'tog-externaleditor' => 'Mặc định dùng trình soạn thảo bên ngoài (chỉ dành cho người thành thạo, cần thiết lập đặc biệt trên máy tính của bạn; [//www.mediawiki.org/wiki/Manual:External_editors?uselang=vi chi tiết])',
'tog-externaldiff' => 'Mặc định dùng trình so sánh bên ngoài (chỉ dành cho người thành thạo, cần thiết lập đặc biệt trên máy tính của bạn; [//www.mediawiki.org/wiki/Manual:External_editors?uselang=vi chi tiết])',
'tog-showjumplinks' => 'Bật liên kết “bước tới” trên đầu trang cho bộ trình duyệt thuần văn bản hay âm thanh',
-'tog-uselivepreview' => 'Xem thử trực tiếp (JavaScript; chưa ổn định)',
+'tog-uselivepreview' => 'Xem thử trực tiếp (cần JavaScript)',
'tog-forceeditsummary' => 'Nhắc tôi khi tôi quên tóm lược sửa đổi',
'tog-watchlisthideown' => 'Ẩn các sửa đổi của tôi khỏi danh sách theo dõi',
'tog-watchlisthidebots' => 'Ẩn các sửa đổi của robot khỏi danh sách theo dõi',
'newwindow' => '(mở cửa sổ mới)',
'cancel' => 'Hủy bỏ',
'moredotdotdot' => 'Thêm nữa…',
+'morenotlisted' => 'Có nhiều hơn danh sách này…',
'mypage' => 'Trang cá nhân',
'mytalk' => 'Tin nhắn',
'anontalk' => 'Thảo luận với IP này',
# E-mail sending
'php-mail-error-unknown' => 'Lỗi không rõ trong hàm PHP mail()',
'user-mail-no-addy' => 'Không có địa chỉ để gửi thư điện tử đến',
+'user-mail-no-body' => 'Không thể gửi thư điện tử rỗng hoặc có nội dung ngắn một cách vô lý.',
# Change password dialog
'resetpass' => 'Đổi mật khẩu',
'pageinfo-recent-authors' => 'Số người dùng sửa đổi gần đây',
'pageinfo-magic-words' => 'Từ thần chú ($1)',
'pageinfo-hidden-categories' => 'Thể loại ẩn ($1)',
-'pageinfo-templates' => 'Bản mẫu được nhúng ($1)',
+'pageinfo-templates' => '{{PLURAL:$1|Bản mẫu|Các bản mẫu}} được nhúng ($1)',
+'pageinfo-transclusions' => '{{PLURAL:$1|Trang|Các trang}} nhúng ($1)',
'pageinfo-toolboxlink' => 'Thông tin trang',
'pageinfo-redirectsto' => 'Đổi hướng đến',
'pageinfo-redirectsto-info' => 'thông tin',
return '1000M';
}
+ public function finalSetup() {
+ # This script needs to be run to build the inital l10n cache. But if
+ # $wgLanguageCode is not 'en', it won't be able to run because there is
+ # no l10n cache. Break the cycle by forcing $wgLanguageCode = 'en'.
+ global $wgLanguageCode;
+ $wgLanguageCode = 'en';
+ return parent::finalSetup();
+ }
+
public function execute() {
global $wgLocalisationCacheConf;
</p>
!! end
+!! article
+prefixed article
+!! text
+Some text
+!! endarticle
+
+!! test
+Bug 43661: Piped links with identical prefixes
+!! input
+[[prefixed article|prefixed articles with spaces]]
+
+[[prefixed article|prefixed articlesaoeu]]
+
+[[Main Page|Main Page test]]
+!! result
+<p><a href="/wiki/Prefixed_article" title="Prefixed article">prefixed articles with spaces</a>
+</p><p><a href="/wiki/Prefixed_article" title="Prefixed article">prefixed articlesaoeu</a>
+</p><p><a href="/wiki/Main_Page" title="Main Page">Main Page test</a>
+</p>
+!! end
+
+
!! test
Link with HTML entity in suffix / tail
!! input
Template as link source
!! input
[[{{linktest2}}]]
+
+[[{{linktest2}}|Main Page]]
+
+[[{{linktest2}}]]Page
!! result
<p><a href="/wiki/Main_Page" title="Main Page">Main Page</a>
+</p><p><a href="/wiki/Main_Page" title="Main Page">Main Page</a>
+</p><p><a href="/wiki/Main_Page" title="Main Page">Main Page</a>Page
</p>
!! end
.DEFAULT: warning
SHELL = /bin/sh
-CONFIG_FILE = $(shell pwd)/suite.xml
+CONFIG_FILE = ${PWD}/suite.xml
PHP = php
PU = ${PHP} phpunit.php --configuration ${CONFIG_FILE} ${FLAGS}
'regex=' => false,
'file=' => false,
'use-filebackend=' => false,
+ 'use-bagostuff=' => false,
'keep-uploads' => false,
'use-normal-tables' => false,
'reuse-db' => false,
--- /dev/null
+<?php
+
+/**
+ * @group Database
+ * @group API
+ */
+class ApiCreateAccountTest extends ApiTestCase {
+ function setUp() {
+ parent::setUp();
+ LoginForm::setCreateaccountToken();
+ }
+
+ /**
+ * Test the account creation API with a valid request. Also
+ * make sure the new account can log in and is valid.
+ */
+ function testValid() {
+ global $wgServer;
+
+ if ( !isset( $wgServer ) ) {
+ $this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' );
+ }
+
+ $password = User::randomPassword();
+
+ $ret = $this->doApiRequest( array(
+ 'action' => 'createaccount',
+ 'name' => 'Apitestnew',
+ 'password' => $password,
+ 'email' => 'test@example.com',
+ 'realname' => 'Test Name'
+ ) );
+
+ $result = $ret[0];
+ $this->assertNotInternalType( 'bool', $result );
+ $this->assertNotInternalType( 'null', $result['createaccount'] );
+
+ // Should first ask for token.
+ $a = $result['createaccount'];
+ $this->assertEquals( 'needtoken', $a['result'] );
+ $token = $a['token'];
+
+ // Finally create the account
+ $ret = $this->doApiRequest( array(
+ 'action' => 'createaccount',
+ 'name' => 'Apitestnew',
+ 'password' => $password,
+ 'token' => $token,
+ 'email' => 'test@domain.test',
+ 'realname' => 'Test Name' ), $ret[2]
+ );
+
+ $result = $ret[0];
+ $this->assertNotInternalType( 'bool', $result );
+ $this->assertEquals( 'success', $result['createaccount']['result'] );
+
+ // Try logging in with the new user.
+ $ret = $this->doApiRequest( array(
+ 'action' => 'login',
+ 'lgname' => 'Apitestnew',
+ 'lgpassword' => $password,
+ )
+ );
+
+ $result = $ret[0];
+ $this->assertNotInternalType( 'bool', $result );
+ $this->assertNotInternalType( 'null', $result['login'] );
+
+ $a = $result['login']['result'];
+ $this->assertEquals( 'NeedToken', $a );
+ $token = $result['login']['token'];
+
+ $ret = $this->doApiRequest( array(
+ 'action' => 'login',
+ 'lgtoken' => $token,
+ 'lgname' => 'Apitestnew',
+ 'lgpassword' => $password,
+ ), $ret[2]
+ );
+
+ $result = $ret[0];
+
+ $this->assertNotInternalType( 'bool', $result );
+ $a = $result['login']['result'];
+
+ $this->assertEquals( 'Success', $a );
+ }
+
+ /**
+ * Make sure requests with no names are invalid.
+ * @expectedException UsageException
+ */
+ function testNoName() {
+ $ret = $this->doApiRequest( array(
+ 'action' => 'createaccount',
+ 'token' => LoginForm::getCreateaccountToken(),
+ 'password' => 'password',
+ ) );
+ }
+
+ /**
+ * Make sure requests with no password are invalid.
+ * @expectedException UsageException
+ */
+ function testNoPassword() {
+ $ret = $this->doApiRequest( array(
+ 'action' => 'createaccount',
+ 'name' => 'testName',
+ 'token' => LoginForm::getCreateaccountToken(),
+ ) );
+ }
+
+ /**
+ * Make sure requests with existing users are invalid.
+ * @expectedException UsageException
+ */
+ function testExistingUser() {
+ $this->doApiRequest( array(
+ 'action' => 'createaccount',
+ 'name' => 'Apitestsysop',
+ 'token' => LoginForm::getCreateaccountToken(),
+ 'password' => 'password',
+ 'email' => 'test@domain.test',
+ ) );
+ }
+
+ /**
+ * Make sure requests with invalid emails are invalid.
+ * @expectedException UsageException
+ */
+ function testInvalidEmail() {
+ $this->doApiRequest( array(
+ 'action' => 'createaccount',
+ 'name' => 'Test User',
+ 'token' => LoginForm::getCreateaccountToken(),
+ 'password' => 'password',
+ 'email' => 'sjlfsjklj',
+ ) );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This class will test BagOStuff.
+ *
+ * @author Matthias Mullie <mmullie@wikimedia.org>
+ */
+class BagOStuffTest extends MediaWikiTestCase {
+ private $cache;
+
+ protected function setUp() {
+ parent::setUp();
+
+ // type defined through parameter
+ if ( $this->getCliArg( 'use-bagostuff=' ) ) {
+ $name = $this->getCliArg( 'use-bagostuff=' );
+
+ $this->cache = ObjectCache::newFromId( $name );
+
+ // no type defined - use simple hash
+ } else {
+ $this->cache = new HashBagOStuff;
+ }
+
+ $this->cache->delete( wfMemcKey( 'test' ) );
+ }
+
+ protected function tearDown() {
+ }
+
+ public function testMerge() {
+ $key = wfMemcKey( 'test' );
+
+ $usleep = 0;
+
+ /**
+ * Callback method: append "merged" to whatever is in cache.
+ *
+ * @param BagOStuff $cache
+ * @param string $key
+ * @param int $existingValue
+ * @use int $usleep
+ * @return int
+ */
+ $callback = function( BagOStuff $cache, $key, $existingValue ) use ( &$usleep ) {
+ // let's pretend this is an expensive callback to test concurrent merge attempts
+ usleep( $usleep );
+
+ if ( $existingValue === false ) {
+ return 'merged';
+ }
+
+ return $existingValue . 'merged';
+ };
+
+ // merge on non-existing value
+ $merged = $this->cache->merge( $key, $callback, 0 );
+ $this->assertTrue( $merged );
+ $this->assertEquals( $this->cache->get( $key ), 'merged' );
+
+ // merge on existing value
+ $merged = $this->cache->merge( $key, $callback, 0 );
+ $this->assertTrue( $merged );
+ $this->assertEquals( $this->cache->get( $key ), 'mergedmerged' );
+
+ /*
+ * Test concurrent merges by forking this process, if:
+ * - not manually called with --use-bagostuff
+ * - pcntl_fork is supported by the system
+ * - cache type will correctly support calls over forks
+ */
+ $fork = (bool) $this->getCliArg( 'use-bagostuff=' );
+ $fork &= function_exists( 'pcntl_fork' );
+ $fork &= !$this->cache instanceof HashBagOStuff;
+ $fork &= !$this->cache instanceof EmptyBagOStuff;
+ $fork &= !$this->cache instanceof MultiWriteBagOStuff;
+ if ( $fork ) {
+ // callback should take awhile now so that we can test concurrent merge attempts
+ $usleep = 5000;
+
+ $pid = pcntl_fork();
+ if ( $pid == -1 ) {
+ // can't fork, ignore this test...
+ } elseif ( $pid ) {
+ // wait a little, making sure that the child process is calling merge
+ usleep( 3000 );
+
+ // attempt a merge - this should fail
+ $merged = $this->cache->merge( $key, $callback, 0, 1 );
+
+ // merge has failed because child process was merging (and we only attempted once)
+ $this->assertFalse( $merged );
+
+ // make sure the child's merge is completed and verify
+ usleep( 3000 );
+ $this->assertEquals( $this->cache->get( $key ), 'mergedmergedmerged' );
+ } else {
+ $this->cache->merge( $key, $callback, 0, 1 );
+
+ // Note: I'm not even going to check if the merge worked, I'll
+ // compare values in the parent process to test if this merge worked.
+ // I'm just going to exit this child process, since I don't want the
+ // child to output any test results (would be rather confusing to
+ // have test output twice)
+ exit;
+ }
+ }
+ }
+}