Index: trunk/extensions/Translate/Translate.i18n.php |
— | — | @@ -257,10 +257,6 @@ |
258 | 258 | 'supportedlanguages-portallink' => '[$1] $2 - $3', |
259 | 259 | 'supportedlanguages-portallink-nocldr' => '[$1] $2', |
260 | 260 | 'supportedlanguages-translators' => '{{PLURAL:$2|Translator|Translators}}: $1', |
261 | | - 'supportedlanguages-noportal-title' => 'No portal namespace defined', |
262 | | - 'supportedlanguages-noportal' => 'The wiki administrator has not defined NS_PORTAL, so this page does not work. |
263 | | -On this page a list of language portals will appear for all portals corresponding with a defined language code and a subpage called "translators". |
264 | | -The subpage "translators" must contain the template [[:{{ns:template}}:User|User]], taking a user name as parameter.', |
265 | 261 | 'supportedlanguages-recenttranslations' => 'recent translations', |
266 | 262 | 'supportedlanguages-count' => '$1 {{PLURAL:$1|language|languages}} in total.', |
267 | 263 | 'supportedlanguages-activity' => '$1: $2 {{PLURAL:$2|edit|edits}} - last edit $3 {{PLURAL:$3|day|days}} ago', |
Index: trunk/extensions/Translate/specials/SpecialSupportedLanguages.php |
— | — | @@ -5,7 +5,7 @@ |
6 | 6 | * @file |
7 | 7 | * @author Niklas Laxström |
8 | 8 | * @author Siebrand Mazeland |
9 | | - * @copyright Copyright © 2010, Niklas Laxström, Siebrand Mazeland |
| 9 | + * @copyright Copyright © 2011, Niklas Laxström, Siebrand Mazeland |
10 | 10 | * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later |
11 | 11 | */ |
12 | 12 | |
— | — | @@ -30,16 +30,10 @@ |
31 | 31 | public function execute( $par ) { |
32 | 32 | global $wgLang, $wgOut, $wgRequest; |
33 | 33 | |
34 | | - // Requires NS_PORTAL. If not present, display error text. |
35 | | - if ( !defined( 'NS_PORTAL' ) ) { |
36 | | - $wgOut->showErrorPage( 'supportedlanguages-noportal-title', 'supportedlanguages-noportal' ); |
37 | | - return; |
38 | | - } |
39 | | - |
40 | 34 | $this->purge = $wgRequest->getVal( 'action' ) === 'purge'; |
41 | 35 | |
42 | 36 | $this->setHeaders(); |
43 | | - $wgOut->addModuleStyles( 'ext.translate.special.supportedlanguages' ); |
| 37 | + TranslateUtils::addModules( $wgOut, 'ext.translate.special.supportedlanguages' ); |
44 | 38 | |
45 | 39 | $cache = wfGetCache( CACHE_ANYTHING ); |
46 | 40 | $cachekey = wfMemcKey( 'translate-supportedlanguages', $wgLang->getCode() ); |
— | — | @@ -64,48 +58,22 @@ |
65 | 59 | $natives = Language::getLanguageNames( false ); |
66 | 60 | ksort( $natives ); |
67 | 61 | |
68 | | - $titles = array(); |
69 | | - foreach ( $natives as $code => $_ ) { |
70 | | - $titles[] = Title::capitalize( $code, NS_PORTAL ) . '/translators'; |
71 | | - } |
| 62 | + $this->outputLanguageCloud( $natives ); |
72 | 63 | |
73 | | - $dbr = wfGetDB( DB_SLAVE ); |
74 | | - $tables = array( 'page', 'revision', 'text' ); |
75 | | - $vars = array_merge( Revision::selectTextFields(), array( 'page_title', 'page_namespace' ), Revision::selectFields() ); |
76 | | - $conds = array( |
77 | | - 'page_latest = rev_id', |
78 | | - 'rev_text_id = old_id', |
79 | | - 'page_namespace' => NS_PORTAL, |
80 | | - 'page_title' => $titles, |
81 | | - ); |
82 | 64 | |
83 | | - $res = $dbr->select( $tables, $vars, $conds, __METHOD__ ); |
84 | | - |
85 | | - $users = array(); |
86 | | - $lb = new LinkBatch; |
87 | | - |
88 | | - foreach ( $res as $row ) { |
89 | | - $rev = new Revision( $row ); |
90 | | - $text = $rev->getText(); |
91 | | - $code = strtolower( preg_replace( '!/translators$!', '', $row->page_title ) ); |
92 | | - |
93 | | - preg_match_all( '!{{[Uu]ser\|([^}|]+)!', $text, $matches, PREG_SET_ORDER ); |
94 | | - foreach ( $matches as $match ) { |
95 | | - $user = Title::capitalize( $match[1], NS_USER ); |
96 | | - $lb->add( NS_USER, $user ); |
97 | | - $lb->add( NS_USER_TALK, $user ); |
98 | | - if ( !isset( $users[$code] ) ) $users[$code] = array(); |
99 | | - $users[$code][] = strtr( $user, '_', ' ' ); |
100 | | - } |
| 65 | + // Requires NS_PORTAL. If not present, display error text. |
| 66 | + if ( !defined( 'NS_PORTAL' ) ) { |
| 67 | + $users = $this->fetchTranslatorsAuto(); |
| 68 | + } else { |
| 69 | + $users = $this->fetchTranslatorsPortal( $natives ); |
101 | 70 | } |
102 | 71 | |
103 | | - $lb->execute(); |
| 72 | + $this->preQueryUsers( $users ); |
104 | 73 | |
105 | 74 | list( $editcounts, $lastedits ) = $this->getUserStats(); |
106 | 75 | global $wgUser; |
107 | 76 | |
108 | 77 | $skin = $wgUser->getSkin(); |
109 | | - $portalBaseText = wfMsg( 'portal' ); |
110 | 78 | |
111 | 79 | // Information to be used inside the foreach loop. |
112 | 80 | $linkInfo['rc']['title'] = SpecialPage::getTitleFor( 'Recentchanges' ); |
— | — | @@ -113,33 +81,29 @@ |
114 | 82 | $linkInfo['stats']['title'] = SpecialPage::getTitleFor( 'LanguageStats' ); |
115 | 83 | $linkInfo['stats']['msg'] = wfMsg( 'languagestats' ); |
116 | 84 | |
117 | | - foreach ( array_keys( $users ) as $code ) { |
118 | | - $portalTitle = Title::makeTitleSafe( NS_PORTAL, $code ); |
119 | | - $portalText = $portalBaseText; |
| 85 | + foreach ( array_keys( $natives ) as $code ) { |
| 86 | + if ( !isset( $users[$code] ) ) continue; |
120 | 87 | |
121 | 88 | // If CLDR is installed, add localised header and link title. |
122 | 89 | if ( $cldrInstalled ) { |
123 | 90 | $headerText = wfMsg( 'supportedlanguages-portallink', $code, $locals[$code], $natives[$code] ); |
124 | | - $portalText .= ' ' . $locals[$code]; |
125 | 91 | } else { |
126 | 92 | // No CLDR, so a less localised header and link title. |
127 | 93 | $headerText = wfMsg( 'supportedlanguages-portallink-nocldr', $code, $natives[$code] ); |
128 | | - $portalText .= ' ' . $natives[$code]; |
129 | 94 | } |
130 | 95 | |
131 | | - $portalLink = $skin->link( |
132 | | - $portalTitle, |
133 | | - $headerText, |
134 | | - array( |
135 | | - 'id' => $code, |
136 | | - 'title' => $portalText |
137 | | - ), |
138 | | - array(), |
139 | | - array( 'known', 'noclasses' ) |
140 | | - ); |
| 96 | + $headerText = htmlspecialchars( $headerText ); |
141 | 97 | |
142 | | - $wgOut->addHTML( "<h2>" . $portalLink . "</h2>" ); |
| 98 | + $wgOut->addHtml( Html::openElement( 'h2', array( 'id' => $code ) ) ); |
| 99 | + if ( defined( 'NS_PORTAL' ) ) { |
| 100 | + $portalTitle = Title::makeTitleSafe( NS_PORTAL, $code ); |
| 101 | + $wgOut->addHtml( $skin->linkKnown( $portalTitle, $headerText ) ); |
| 102 | + } else { |
| 103 | + $wgOut->addHtml( $headerText ); |
| 104 | + } |
143 | 105 | |
| 106 | + $wgOut->addHTML( "</h2>" ); |
| 107 | + |
144 | 108 | // Add useful links for language stats and recent changes for the language. |
145 | 109 | $links = array(); |
146 | 110 | $links[] = $skin->link( |
— | — | @@ -174,6 +138,131 @@ |
175 | 139 | $cache->set( $cachekey, $wgOut->getHTML(), 3600 ); |
176 | 140 | } |
177 | 141 | |
| 142 | + protected function languageCloud() { |
| 143 | + global $wgTranslateMessageNamespaces; |
| 144 | + |
| 145 | + $cache = wfGetCache( CACHE_ANYTHING ); |
| 146 | + $cachekey = wfMemcKey( 'translate-supportedlanguages-language-cloud' ); |
| 147 | + $data = $cache->get( $cachekey ); |
| 148 | + if ( !$this->purge && is_array( $data ) ) { |
| 149 | + return $data; |
| 150 | + } |
| 151 | + |
| 152 | + $dbr = wfGetDB( DB_SLAVE ); |
| 153 | + $tables = array( 'recentchanges' ); |
| 154 | + $fields = array( 'substring_index(rc_title, \'/\', -1) as lang', 'count(*) as count' ); |
| 155 | + $conds = array( |
| 156 | + 'rc_title' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() ), |
| 157 | + 'rc_namespace' => $wgTranslateMessageNamespaces, |
| 158 | + 'rc_timestamp > ' . $dbr->timestamp( TS_DB, wfTimeStamp( TS_UNIX ) - 60*60*24*180 ), |
| 159 | + ); |
| 160 | + $options = array( 'GROUP BY' => 'lang', 'HAVING' => 'count > 20' ); |
| 161 | + |
| 162 | + $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options ); |
| 163 | + |
| 164 | + $data = array(); |
| 165 | + foreach ( $res as $row ) { |
| 166 | + $data[$row->lang] = $row->count; |
| 167 | + } |
| 168 | + |
| 169 | + $cache->set( $cachekey, $data, 3600 ); |
| 170 | + return $data; |
| 171 | + } |
| 172 | + |
| 173 | + protected function fetchTranslatorsAuto() { |
| 174 | + global $wgTranslateMessageNamespaces; |
| 175 | + |
| 176 | + $cache = wfGetCache( CACHE_ANYTHING ); |
| 177 | + $cachekey = wfMemcKey( 'translate-supportedlanguages-translator-list' ); |
| 178 | + $data = $cache->get( $cachekey ); |
| 179 | + if ( !$this->purge && is_array( $data ) ) { |
| 180 | + return $data; |
| 181 | + } |
| 182 | + |
| 183 | + $dbr = wfGetDB( DB_SLAVE ); |
| 184 | + $tables = array( 'page', 'revision' ); |
| 185 | + $fields = array( 'rev_user_text', 'substring_index(page_title, \'/\', -1) as lang', 'count(page_id) as count' ); |
| 186 | + $conds = array( |
| 187 | + 'page_title' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() ), |
| 188 | + 'page_namespace' => $wgTranslateMessageNamespaces, |
| 189 | + 'page_id=rev_page', |
| 190 | + ); |
| 191 | + $options = array( 'GROUP BY' => 'rev_user_text, lang' ); |
| 192 | + |
| 193 | + $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options ); |
| 194 | + |
| 195 | + $data = array(); |
| 196 | + foreach ( $res as $row ) { |
| 197 | + $data[$row->lang][$row->rev_user_text] = $row->count; |
| 198 | + } |
| 199 | + |
| 200 | + $cache->set( $cachekey, $data, 3600 ); |
| 201 | + return $data; |
| 202 | + } |
| 203 | + |
| 204 | + public function fetchTranslatorsPortal( $natives ) { |
| 205 | + $titles = array(); |
| 206 | + foreach ( $natives as $code => $_ ) { |
| 207 | + $titles[] = Title::capitalize( $code, NS_PORTAL ) . '/translators'; |
| 208 | + } |
| 209 | + |
| 210 | + $dbr = wfGetDB( DB_SLAVE ); |
| 211 | + $tables = array( 'page', 'revision', 'text' ); |
| 212 | + $vars = array_merge( Revision::selectTextFields(), array( 'page_title', 'page_namespace' ), Revision::selectFields() ); |
| 213 | + $conds = array( |
| 214 | + 'page_latest = rev_id', |
| 215 | + 'rev_text_id = old_id', |
| 216 | + 'page_namespace' => NS_PORTAL, |
| 217 | + 'page_title' => $titles, |
| 218 | + ); |
| 219 | + |
| 220 | + $res = $dbr->select( $tables, $vars, $conds, __METHOD__ ); |
| 221 | + |
| 222 | + $users = array(); |
| 223 | + $lb = new LinkBatch; |
| 224 | + |
| 225 | + foreach ( $res as $row ) { |
| 226 | + $rev = new Revision( $row ); |
| 227 | + $text = $rev->getText(); |
| 228 | + $code = strtolower( preg_replace( '!/translators$!', '', $row->page_title ) ); |
| 229 | + |
| 230 | + preg_match_all( '!{{[Uu]ser\|([^}|]+)!', $text, $matches, PREG_SET_ORDER ); |
| 231 | + foreach ( $matches as $match ) { |
| 232 | + $user = Title::capitalize( $match[1], NS_USER ); |
| 233 | + $lb->add( NS_USER, $user ); |
| 234 | + $lb->add( NS_USER_TALK, $user ); |
| 235 | + if ( !isset( $users[$code] ) ) $users[$code] = array(); |
| 236 | + $users[$code][strtr( $user, '_', ' ' )] = -1; |
| 237 | + } |
| 238 | + } |
| 239 | + |
| 240 | + $lb->execute(); |
| 241 | + return $users; |
| 242 | + } |
| 243 | + |
| 244 | + |
| 245 | + protected function outputLanguageCloud( $names ) { |
| 246 | + global $wgOut; |
| 247 | + |
| 248 | + $langs = $this->languageCloud(); |
| 249 | + $wgOut->addHtml( '<div class="tagcloud">' ); |
| 250 | + $langs = $this->shuffle_assoc( $langs ); |
| 251 | + foreach ( $langs as $k => $v ) { |
| 252 | + $name = isset( $names[$k] ) ? $names[$k] : $k; |
| 253 | + $size = round( log( $v ) * 20 ) + 10; |
| 254 | + |
| 255 | + $params = array( |
| 256 | + 'href' => "#$k", |
| 257 | + 'class' => 'tag', |
| 258 | + 'style' => "font-size:$size%", |
| 259 | + ); |
| 260 | + |
| 261 | + $tag = Html::element( 'a', $params, $name ); |
| 262 | + $wgOut->addHtml( $tag ."\n" ); |
| 263 | + } |
| 264 | + $wgOut->addHtml( '</div>' ); |
| 265 | + } |
| 266 | + |
178 | 267 | protected function makeUserList( $users, $editcounts, $lastedits ) { |
179 | 268 | global $wgOut, $wgLang, $wgUser; |
180 | 269 | $skin = $wgUser->getSkin(); |
— | — | @@ -184,14 +273,17 @@ |
185 | 274 | // longer than this is just inactive |
186 | 275 | $period = 180; |
187 | 276 | |
188 | | - foreach ( $users as $index => $username ) { |
| 277 | + $links = array(); |
| 278 | + |
| 279 | + foreach ( $users as $username => $count ) { |
189 | 280 | $title = Title::makeTitleSafe( NS_USER, $username ); |
190 | 281 | $enc = htmlspecialchars( $username ); |
191 | 282 | |
192 | 283 | $attribs = array(); |
193 | 284 | $styles = array(); |
194 | 285 | if ( isset( $editcounts[$username] ) ) { |
195 | | - $count = $editcounts[$username]; |
| 286 | + if ( $count === -1 ) $count = $editcounts[$username]; |
| 287 | + |
196 | 288 | $styles['font-size'] = round( log( $count, 10 ) * 30 ) + 70 . '%'; |
197 | 289 | |
198 | 290 | $last = wfTimestamp( TS_UNIX ) - $lastedits[$username]; |
— | — | @@ -208,14 +300,14 @@ |
209 | 301 | $stylestr = $this->formatStyle( $styles ); |
210 | 302 | if ( $stylestr ) $attribs['style'] = $stylestr; |
211 | 303 | |
212 | | - $users[$index] = $skin->link( $title, $enc, $attribs ); |
| 304 | + $links[] = $skin->link( $title, $enc, $attribs ); |
213 | 305 | } |
214 | 306 | |
215 | 307 | $wgOut->addHTML( "<p class='mw-translate-spsl-translators'>" . wfMsgExt( |
216 | 308 | 'supportedlanguages-translators', |
217 | 309 | 'parsemag', |
218 | | - $wgLang->listToText( $users ), |
219 | | - count( $users ) |
| 310 | + $wgLang->listToText( $links ), |
| 311 | + count( $links ) |
220 | 312 | ) . "</p>\n" ); |
221 | 313 | } |
222 | 314 | |
— | — | @@ -270,4 +362,29 @@ |
271 | 363 | |
272 | 364 | return $red . $green . $blue; |
273 | 365 | } |
| 366 | + |
| 367 | + function shuffle_assoc($list) { |
| 368 | + if (!is_array($list)) return $list; |
| 369 | + |
| 370 | + $keys = array_keys($list); |
| 371 | + shuffle($keys); |
| 372 | + $random = array(); |
| 373 | + foreach ($keys as $key) |
| 374 | + $random[$key] = $list[$key]; |
| 375 | + |
| 376 | + return $random; |
| 377 | + } |
| 378 | + |
| 379 | + protected function preQueryUsers( $users ) { |
| 380 | + $lb = new LinkBatch; |
| 381 | + foreach ( $users as $translators ) { |
| 382 | + foreach ( $translators as $user => $count ) { |
| 383 | + $user = Title::capitalize( $user, NS_USER ); |
| 384 | + $lb->add( NS_USER, $user ); |
| 385 | + $lb->add( NS_USER_TALK, $user ); |
| 386 | + } |
| 387 | + } |
| 388 | + $lb->execute(); |
| 389 | + } |
| 390 | + |
274 | 391 | } |