Index: trunk/extensions/Translate/scripts/groupStatistics.php |
— | — | @@ -372,12 +372,7 @@ |
373 | 373 | $rows[$code] = array(); |
374 | 374 | } |
375 | 375 | |
376 | | -$cache = new ArrayMemoryCache( 'groupstats' ); |
377 | | - |
378 | 376 | foreach ( $groups as $groupName => $g ) { |
379 | | - // Initialise messages |
380 | | - $collection = $g->initCollection( 'en' ); |
381 | | - |
382 | 377 | // Perform the statistic calculations on every language |
383 | 378 | foreach ( $languages as $code => $name ) { |
384 | 379 | // Skip list |
— | — | @@ -395,27 +390,8 @@ |
396 | 391 | continue; |
397 | 392 | } |
398 | 393 | |
399 | | - $incache = $cache->get( $groupName, $code ); |
400 | | - if ( $incache !== false ) { |
401 | | - list( $fuzzy, $translated, $total ) = $incache; |
402 | | - } else { |
403 | | - $collection->resetForNewLanguage( $code ); |
404 | | - $collection->filter( 'ignored' ); |
405 | | - $collection->filter( 'optional' ); |
406 | | - // Store the count of real messages for later calculation. |
407 | | - $total = count( $collection ); |
| 394 | + list( $total, $translated, $fuzzy ) = MessageGroupStats::forItem( $groupName, $code ); |
408 | 395 | |
409 | | - // Count fuzzy first |
410 | | - $collection->filter( 'fuzzy' ); |
411 | | - $fuzzy = $total - count( $collection ); |
412 | | - |
413 | | - // Count the completion percent |
414 | | - $collection->filter( 'hastranslation', false ); |
415 | | - $translated = count( $collection ); |
416 | | - |
417 | | - $cache->set( $groupName, $code, array( $fuzzy, $translated, $total ) ); |
418 | | - } |
419 | | - |
420 | 396 | $rows[$code][] = array( false, $translated, $total ); |
421 | 397 | |
422 | 398 | if ( isset( $options['fuzzy'] ) ) { |
— | — | @@ -423,8 +399,6 @@ |
424 | 400 | } |
425 | 401 | } |
426 | 402 | |
427 | | - $cache->commit(); // Do not keep open too long to avoid concurrent access |
428 | | - |
429 | 403 | unset( $collection ); |
430 | 404 | } |
431 | 405 | |
Index: trunk/extensions/Translate/tag/SpecialPageTranslation.php |
— | — | @@ -535,7 +535,7 @@ |
536 | 536 | |
537 | 537 | // Re-generate caches |
538 | 538 | $page->getTranslationPercentages( /*re-generate*/ true ); |
539 | | - ArrayMemoryCache::factory( 'groupstats' )->clearGroup( $page->getMessageGroupId() ); |
| 539 | + MessageGroupStats::clearGroup( $page->getMessageGroupId() ); |
540 | 540 | MessageIndexRebuilder::execute(); |
541 | 541 | MessageGroups::clearCache(); |
542 | 542 | return false; |
Index: trunk/extensions/Translate/TranslateEditAddons.php |
— | — | @@ -274,7 +274,8 @@ |
275 | 275 | * @return \bool If string contains fuzzy string. |
276 | 276 | */ |
277 | 277 | public static function hasFuzzyString( $text ) { |
278 | | - return strpos( $text, TRANSLATE_FUZZY ) !== false; |
| 278 | + #wfDeprecated( __METHOD__, '1.19' ); |
| 279 | + return MessageHandle::hasFuzzyString( $text ); |
279 | 280 | } |
280 | 281 | |
281 | 282 | /** |
— | — | @@ -283,24 +284,11 @@ |
284 | 285 | * @return \bool If title is marked fuzzy. |
285 | 286 | */ |
286 | 287 | public static function isFuzzy( Title $title ) { |
287 | | - $dbr = wfGetDB( DB_SLAVE ); |
288 | | - |
289 | | - $tables = array( 'page', 'revtag' ); |
290 | | - $field = 'rt_type'; |
291 | | - $conds = array( |
292 | | - 'page_namespace' => $title->getNamespace(), |
293 | | - 'page_title' => $title->getDBkey(), |
294 | | - 'rt_type' => RevTag::getType( 'fuzzy' ), |
295 | | - 'page_id=rt_page', |
296 | | - 'page_latest=rt_revision' |
297 | | - ); |
298 | | - |
299 | | - $res = $dbr->selectField( $tables, $field, $conds, __METHOD__ ); |
300 | | - |
301 | | - return $res !== false; |
| 288 | + #wfDeprecated( __METHOD__, '1.19' ); |
| 289 | + $handle = new MessageHandle( $title ); |
| 290 | + return $handle->isFuzzy(); |
302 | 291 | } |
303 | 292 | |
304 | | - |
305 | 293 | /** |
306 | 294 | * Check if a title is in a message namespace. |
307 | 295 | * @param $title Title |
— | — | @@ -346,97 +334,110 @@ |
347 | 335 | $minor, $_, $_, $flags, $revision |
348 | 336 | ) { |
349 | 337 | $title = $article->getTitle(); |
| 338 | + $handle = new MessageHandle( $title ); |
350 | 339 | |
351 | | - if ( !self::isMessageNamespace( $title ) ) { |
| 340 | + if ( !$handle->isValid() ) { |
352 | 341 | return true; |
353 | 342 | } |
354 | 343 | |
355 | | - list( $key, $code, $group ) = self::getKeyCodeGroup( $title ); |
356 | | - |
357 | | - // Unknown message, do not handle. |
358 | | - if ( !$group || !$code ) { |
359 | | - return true; |
| 344 | + // Update it. |
| 345 | + if ( $revision === null ) { |
| 346 | + $rev = $article->getTitle()->getLatestRevId(); |
| 347 | + } else { |
| 348 | + $rev = $revision->getID(); |
360 | 349 | } |
361 | 350 | |
362 | | - $groups = TranslateUtils::messageKeyToGroups( $title->getNamespace(), $key ); |
363 | | - $cache = new ArrayMemoryCache( 'groupstats' ); |
| 351 | + $fuzzy = self::checkNeedsFuzzy( $handle, $text ); |
| 352 | + self::updateFuzzyTag( $title, $rev, $fuzzy ); |
| 353 | + MessageGroupStats::clear( $handle ); |
364 | 354 | |
365 | | - foreach ( $groups as $g ) { |
366 | | - $cache->clear( $g, $code ); |
| 355 | + if ( $fuzzy === false ) { |
| 356 | + // Fuzzy versions are not real translations |
| 357 | + self::updateTransverTag( $handle, $rev ); |
367 | 358 | } |
368 | 359 | |
| 360 | + return true; |
| 361 | + } |
| 362 | + |
| 363 | + protected static function checkNeedsFuzzy( MessageHandle $handle, $text ) { |
369 | 364 | // Check for explicit tag. |
370 | 365 | $fuzzy = self::hasFuzzyString( $text ); |
371 | 366 | |
372 | | - // Check for problems, but only if not fuzzy already. |
| 367 | + // Docs are exempt for checks |
373 | 368 | global $wgTranslateDocumentationLanguageCode; |
374 | | - if ( $code !== $wgTranslateDocumentationLanguageCode ) { |
375 | | - $checker = $group->getChecker(); |
| 369 | + if ( $code === $wgTranslateDocumentationLanguageCode ) { |
| 370 | + return $fuzzy; |
| 371 | + } |
| 372 | + |
| 373 | + // Not all groups have checkers |
| 374 | + $group = $handle->getGroup(); |
| 375 | + $checker = $group->getChecker(); |
| 376 | + if ( !$checker ) { |
| 377 | + return $fuzzy; |
| 378 | + } |
376 | 379 | |
377 | | - if ( $checker ) { |
378 | | - $en = $group->getMessage( $key, $group->getSourceLanguage() ); |
379 | | - $message = new FatMessage( $key, $en ); |
380 | | - /** |
381 | | - * Take the contents from edit field as a translation. |
382 | | - */ |
383 | | - $message->setTranslation( $text ); |
| 380 | + $en = $group->getMessage( $key, $group->getSourceLanguage() ); |
| 381 | + $message = new FatMessage( $key, $en ); |
| 382 | + // Take the contents from edit field as a translation. |
| 383 | + $message->setTranslation( $text ); |
384 | 384 | |
385 | | - $checks = $checker->checkMessage( $message, $code ); |
386 | | - if ( count( $checks ) ) { |
387 | | - $fuzzy = true; |
388 | | - } |
389 | | - } |
| 385 | + $checks = $checker->checkMessage( $message, $code ); |
| 386 | + if ( count( $checks ) ) { |
| 387 | + $fuzzy = true; |
390 | 388 | } |
| 389 | + return $fuzzy; |
| 390 | + } |
391 | 391 | |
392 | | - // Update it. |
393 | | - if ( $revision === null ) { |
394 | | - $rev = $article->getTitle()->getLatestRevId(); |
395 | | - } else { |
396 | | - $rev = $revision->getID(); |
397 | | - } |
398 | | - |
399 | | - // begin fuzzy tag. |
| 392 | + protected static function updateFuzzyTag( Title $title, $revision, $fuzzy ) { |
400 | 393 | $dbw = wfGetDB( DB_MASTER ); |
401 | 394 | |
402 | 395 | $conds = array( |
403 | | - 'rt_page' => $article->getTitle()->getArticleId(), |
| 396 | + 'rt_page' => $title->getArticleId(), |
404 | 397 | 'rt_type' => RevTag::getType( 'fuzzy' ), |
405 | | - 'rt_revision' => $rev |
| 398 | + 'rt_revision' => $revision |
406 | 399 | ); |
407 | | - // Remove any existing fuzzy tags for this revision |
408 | | - $dbw->delete( 'revtag', $conds, __METHOD__ ); |
409 | | - |
410 | | - // Add the fuzzy tag if needed. |
| 400 | + |
| 401 | + // Replace the existing fuzzy tag, if any |
411 | 402 | if ( $fuzzy !== false ) { |
412 | | - $dbw->insert( 'revtag', $conds, __METHOD__ ); |
| 403 | + $index = array_keys( $conds ); |
| 404 | + $dbw->replace( 'revtag', array( $index ), $conds, __METHOD__ ); |
| 405 | + } else { |
| 406 | + $dbw->delete( 'revtag', $conds, __METHOD__ ); |
413 | 407 | } |
| 408 | + } |
414 | 409 | |
415 | | - // Diffs for changed messages. |
416 | | - if ( $fuzzy !== false ) { |
417 | | - return true; |
418 | | - } |
419 | 410 | |
| 411 | + /** |
| 412 | + * Adds tag which identifies the revision of source message at that time. |
| 413 | + * This is used to show diff against current version of source message |
| 414 | + * when updating a translation. |
| 415 | + */ |
| 416 | + protected static function updateTransverTag( MessageHandle $handle, $revision ) { |
| 417 | + $group = $handle->getGroup(); |
420 | 418 | if ( $group instanceof WikiPageMessageGroup ) { |
421 | | - return true; |
| 419 | + // WikiPageMessageGroup has different method |
| 420 | + return; |
422 | 421 | } |
423 | 422 | |
| 423 | + $title = $handle->getTitle(); |
| 424 | + $fullKey = $handle->getKey() . '/' . $group->getSourceLanguage(); |
| 425 | + $definitionTitle = Title::makeTitleSafe( $title->getNamespace(), $fullkey ); |
| 426 | + if ( !$definitionTitle || !$definitionTitle->exists() ) { |
| 427 | + return; |
| 428 | + } |
424 | 429 | |
| 430 | + $definitionRevision = $definitionTitle->getLatestRevID(); |
425 | 431 | |
426 | | - $definitionTitle = Title::makeTitleSafe( $title->getNamespace(), "$key/" . $group->getSourceLanguage() ); |
427 | | - if ( $definitionTitle && $definitionTitle->exists() ) { |
428 | | - $definitionRevision = $definitionTitle->getLatestRevID(); |
| 432 | + $dbw = wfGetDB( DB_MASTER ); |
429 | 433 | |
430 | | - $conds = array( |
431 | | - 'rt_page' => $title->getArticleId(), |
432 | | - 'rt_type' => RevTag::getType( 'tp:transver' ), |
433 | | - 'rt_revision' => $rev, |
434 | | - 'rt_value' => $definitionRevision, |
435 | | - ); |
436 | | - $index = array( 'rt_type', 'rt_page', 'rt_revision' ); |
437 | | - $dbw->replace( 'revtag', array( $index ), $conds, __METHOD__ ); |
438 | | - } |
439 | | - |
440 | | - return true; |
| 434 | + $conds = array( |
| 435 | + 'rt_page' => $title->getArticleId(), |
| 436 | + 'rt_type' => RevTag::getType( 'tp:transver' ), |
| 437 | + 'rt_revision' => $revision, |
| 438 | + 'rt_value' => $definitionRevision, |
| 439 | + ); |
| 440 | + $index = array( 'rt_type', 'rt_page', 'rt_revision' ); |
| 441 | + $dbw->replace( 'revtag', array( $index ), $conds, __METHOD__ ); |
441 | 442 | } |
442 | 443 | |
443 | 444 | public static function preserveWhitespaces( $text ) { |
Index: trunk/extensions/Translate/Translate.php |
— | — | @@ -92,6 +92,10 @@ |
93 | 93 | $wgHooks['SkinTemplateTabs'][] = 'TranslateEditAddons::tabs'; |
94 | 94 | $wgHooks['LanguageGetTranslatedLanguageNames'][] = 'TranslateEditAddons::translateMessageDocumentationLanguage'; |
95 | 95 | $wgHooks['ArticlePrepareTextForEdit'][] = 'TranslateEditAddons::disablePreSaveTransform'; |
| 96 | +// Hook for database schema. |
| 97 | +$wgHooks['LoadExtensionSchemaUpdates'][] = 'PageTranslationHooks::schemaUpdates'; |
| 98 | +// Fuzzy tags for speed. |
| 99 | +$wgHooks['ArticleSaveComplete'][] = 'TranslateEditAddons::onSave'; |
96 | 100 | |
97 | 101 | // Custom preferences |
98 | 102 | $wgDefaultUserOptions['translate'] = 0; |
Index: trunk/extensions/Translate/TranslateHooks.php |
— | — | @@ -39,12 +39,6 @@ |
40 | 40 | global $wgReservedUsernames, $wgTranslateFuzzyBotName; |
41 | 41 | $wgReservedUsernames[] = $wgTranslateFuzzyBotName; |
42 | 42 | |
43 | | - // Hook for database schema. |
44 | | - $wgHooks['LoadExtensionSchemaUpdates'][] = 'PageTranslationHooks::schemaUpdates'; |
45 | | - |
46 | | - // Fuzzy tags for speed. |
47 | | - $wgHooks['ArticleSaveComplete'][] = 'TranslateEditAddons::onSave'; |
48 | | - |
49 | 43 | // Page translation setup check and init if enabled. |
50 | 44 | global $wgEnablePageTranslation; |
51 | 45 | if ( $wgEnablePageTranslation ) { |
Index: trunk/extensions/Translate/TranslateTasks.php |
— | — | @@ -222,15 +222,7 @@ |
223 | 223 | $this->collection->setInfile( $this->group->load( $code ) ); |
224 | 224 | $this->collection->filter( 'ignored' ); |
225 | 225 | $this->collection->filter( 'optional' ); |
226 | | - |
227 | | - // Update the cache while we are at it. |
228 | | - $total = count( $this->collection ); |
229 | 226 | $this->collection->filter( 'translated' ); |
230 | | - $translated = $total - count( $this->collection ); |
231 | | - $fuzzy = count( $this->collection->getTags( 'fuzzy' ) ); |
232 | | - |
233 | | - $cache = new ArrayMemoryCache( 'groupstats' ); |
234 | | - $cache->set( $this->group->getID(), $code, array( $fuzzy, $translated, $total ) ); |
235 | 227 | } |
236 | 228 | } |
237 | 229 | |
Index: trunk/extensions/Translate/_autoload.php |
— | — | @@ -94,8 +94,6 @@ |
95 | 95 | $wgAutoloadClasses['StringMatcher'] = $dir . 'utils/StringMatcher.php'; |
96 | 96 | $wgAutoloadClasses['FCFontFinder'] = $dir . 'utils/Font.php'; |
97 | 97 | |
98 | | -$wgAutoloadClasses['ArrayMemoryCache'] = $dir . 'utils/MemoryCache.php'; |
99 | | - |
100 | 98 | $wgAutoloadClasses['TranslatePreferences'] = $dir . 'utils/UserToggles.php'; |
101 | 99 | $wgAutoloadClasses['TranslateToolbox'] = $dir . 'utils/ToolBox.php'; |
102 | 100 | |
Index: trunk/extensions/Translate/utils/MemoryCache.php |
— | — | @@ -1,116 +0,0 @@ |
2 | | -<?php |
3 | | -/** |
4 | | - * Code for caching translation completion percentages. |
5 | | - * |
6 | | - * @file |
7 | | - * @author Niklas Laxström |
8 | | - * @copyright Copyright © 2009-2010, Niklas Laxström |
9 | | - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later |
10 | | - */ |
11 | | - |
12 | | -/** |
13 | | - * Class for caching translation completion percentages. |
14 | | - * @todo Figure out a better name. |
15 | | - * @todo Tries to be generic, but is not. |
16 | | - * @ingroup Stats |
17 | | - */ |
18 | | -class ArrayMemoryCache { |
19 | | - /// Key for the data stored |
20 | | - protected $key; |
21 | | - /// Memory cache wrapper |
22 | | - protected $memc; |
23 | | - /// Object cache of the data |
24 | | - protected $cache; |
25 | | - |
26 | | - /** |
27 | | - * Constructor |
28 | | - * @param $table \string Name of the cache. |
29 | | - */ |
30 | | - public function __construct( $table ) { |
31 | | - $this->key = wfMemcKey( $table ); |
32 | | - |
33 | | - $cacher = wfGetCache( CACHE_MEMCACHED ); |
34 | | - if ( $cacher instanceof FakeMemCachedClient ) { |
35 | | - $cacher = wfGetCache( CACHE_DB ); |
36 | | - } |
37 | | - |
38 | | - $this->memc = $cacher; |
39 | | - } |
40 | | - |
41 | | - /// Destructor |
42 | | - public function __destruct() { |
43 | | - $this->save(); |
44 | | - } |
45 | | - |
46 | | - /** |
47 | | - * @copydoc ArrayMemoryCache::__construct() |
48 | | - * Static factory function for the constructor. |
49 | | - */ |
50 | | - public static function factory( $table ) { |
51 | | - // __CLASS__ doesn't work, but this is PHP |
52 | | - return new ArrayMemoryCache( $table ); |
53 | | - } |
54 | | - |
55 | | - public function get( $group, $code ) { |
56 | | - $this->load(); |
57 | | - |
58 | | - if ( !isset( $this->cache[$group][$code] ) ) { |
59 | | - return false; |
60 | | - } |
61 | | - |
62 | | - return explode( ',', $this->cache[$group][$code] ); |
63 | | - } |
64 | | - |
65 | | - public function set( $group, $code, $value ) { |
66 | | - $this->load(); |
67 | | - |
68 | | - if ( !isset( $this->cache[$group] ) ) { |
69 | | - $this->cache[$group] = array(); |
70 | | - } |
71 | | - |
72 | | - $this->cache[$group][$code] = implode( ',', $value ); |
73 | | - } |
74 | | - |
75 | | - public function clear( $group, $code ) { |
76 | | - $this->load(); |
77 | | - |
78 | | - if ( isset( $this->cache[$group][$code] ) ) { |
79 | | - unset( $this->cache[$group][$code] ); |
80 | | - } |
81 | | - |
82 | | - if ( isset( $this->cache[$group] ) && !count( $this->cache[$group] ) ) { |
83 | | - unset( $this->cache[$group] ); |
84 | | - } |
85 | | - } |
86 | | - |
87 | | - public function clearGroup( $group ) { |
88 | | - $this->load(); |
89 | | - unset( $this->cache[$group] ); |
90 | | - } |
91 | | - |
92 | | - public function clearAll() { |
93 | | - $this->load(); |
94 | | - $this->cache = array(); |
95 | | - } |
96 | | - |
97 | | - public function commit() { |
98 | | - $this->save(); |
99 | | - } |
100 | | - |
101 | | - |
102 | | - protected function load() { |
103 | | - if ( $this->cache === null ) { |
104 | | - $this->cache = $this->memc->get( $this->key ); |
105 | | - |
106 | | - if ( !is_array( $this->cache ) ) { |
107 | | - $this->cache = array(); |
108 | | - } |
109 | | - } |
110 | | - } |
111 | | - |
112 | | - protected function save() { |
113 | | - if ( $this->cache !== null ) { |
114 | | - $this->memc->set( $this->key, $this->cache ); |
115 | | - } |
116 | | - } |
117 | | -} |
Index: trunk/extensions/Translate/utils/MessageHandle.php |
— | — | @@ -99,4 +99,37 @@ |
100 | 100 | return $this->title; |
101 | 101 | } |
102 | 102 | |
| 103 | + /** |
| 104 | + * Check if a string contains the fuzzy string. |
| 105 | + * |
| 106 | + * @param $text \string Arbitrary text |
| 107 | + * @return \bool If string contains fuzzy string. |
| 108 | + */ |
| 109 | + public static function hasFuzzyString( $text ) { |
| 110 | + return strpos( $text, TRANSLATE_FUZZY ) !== false; |
| 111 | + } |
| 112 | + |
| 113 | + /** |
| 114 | + * Check if a title is marked as fuzzy. |
| 115 | + * @param $title Title |
| 116 | + * @return \bool If title is marked fuzzy. |
| 117 | + */ |
| 118 | + public function isFuzzy() { |
| 119 | + $dbr = wfGetDB( DB_SLAVE ); |
| 120 | + |
| 121 | + $tables = array( 'page', 'revtag' ); |
| 122 | + $field = 'rt_type'; |
| 123 | + $conds = array( |
| 124 | + 'page_namespace' => $this->title->getNamespace(), |
| 125 | + 'page_title' => $this->title->getDBkey(), |
| 126 | + 'rt_type' => RevTag::getType( 'fuzzy' ), |
| 127 | + 'page_id=rt_page', |
| 128 | + 'page_latest=rt_revision' |
| 129 | + ); |
| 130 | + |
| 131 | + $res = $dbr->selectField( $tables, $field, $conds, __METHOD__ ); |
| 132 | + |
| 133 | + return $res !== false; |
| 134 | + } |
| 135 | + |
103 | 136 | } |
Index: trunk/extensions/Translate/utils/MessageIndexRebuilder.php |
— | — | @@ -59,10 +59,7 @@ |
60 | 60 | foreach ( (array) $groups as $group ) $changes[$group] = true; |
61 | 61 | } |
62 | 62 | |
63 | | - $cache = new ArrayMemoryCache( 'groupstats' ); |
64 | | - foreach ( $changes as $key => $_ ) { |
65 | | - $cache->clearGroup( $key ); |
66 | | - } |
| 63 | + MessageGroupStats::clearGroup( array_keys( $changes ) ); |
67 | 64 | } |
68 | 65 | |
69 | 66 | protected static function checkAndAdd( &$hugearray, $g, $ignore = false ) { |
Index: trunk/extensions/Translate/utils/MessageGroupStats.php |
— | — | @@ -34,6 +34,26 @@ |
35 | 35 | } |
36 | 36 | |
37 | 37 | /** |
| 38 | + * Returns stats for given group in given language. |
| 39 | + * @param $id string Group id |
| 40 | + * @param $code string Language code |
| 41 | + * @return Array |
| 42 | + */ |
| 43 | + public static function forItem( $id, $code ) { |
| 44 | + $stats = array(); |
| 45 | + $res = self::selectRowsIdLang( $id, $code ); |
| 46 | + $stats = self::extractResults( $res, $stats ); |
| 47 | + |
| 48 | + $group = MessageGroups::getGroup( $id ); |
| 49 | + |
| 50 | + if ( !isset( $stats[$id][$code] ) ) { |
| 51 | + $stats[$id][$code] = self::forItemInternal( $stats, $group, $code ); |
| 52 | + } |
| 53 | + |
| 54 | + return $stats[$id][$code]; |
| 55 | + } |
| 56 | + |
| 57 | + /** |
38 | 58 | * Returns stats for all groups in given language. |
39 | 59 | * @param $code string Language code |
40 | 60 | * @return Array |
— | — | @@ -76,6 +96,28 @@ |
77 | 97 | return $stats; |
78 | 98 | } |
79 | 99 | |
| 100 | + public static function clear( MessageHandle $handle ) { |
| 101 | + $dbw = wfGetDB( DB_MASTER ); |
| 102 | + $conds = array( |
| 103 | + 'tgs_group' => $handle->getGroupIds(), |
| 104 | + 'tgs_lang' => $handle->getCode(), |
| 105 | + ); |
| 106 | + |
| 107 | + $dbw->delete( self::TABLE, $conds, __METHOD__ ); |
| 108 | + } |
| 109 | + |
| 110 | + public static function clearGroup( $id ) { |
| 111 | + $dbw = wfGetDB( DB_MASTER ); |
| 112 | + $conds = array( 'tgs_group' => $id ); |
| 113 | + $dbw->delete( self::TABLE, $conds, __METHOD__ ); |
| 114 | + } |
| 115 | + |
| 116 | + public static function clearLanguage( $code ) { |
| 117 | + $dbw = wfGetDB( DB_MASTER ); |
| 118 | + $conds = array( 'tgs_lang' => $code ); |
| 119 | + $dbw->delete( self::TABLE, $conds, __METHOD__ ); |
| 120 | + } |
| 121 | + |
80 | 122 | /** |
81 | 123 | * Purges all cached stats. |
82 | 124 | */ |
— | — | @@ -91,6 +133,24 @@ |
92 | 134 | return $stats; |
93 | 135 | } |
94 | 136 | |
| 137 | + public static function update( MessageHandle $handle, $changes = array() ) { |
| 138 | + $dbw = wfGetDB( DB_MASTER ); |
| 139 | + $conds = array( |
| 140 | + 'tgs_group' => $handle->getGroupIds(), |
| 141 | + 'tgs_lang' => $handle->getCode(), |
| 142 | + ); |
| 143 | + |
| 144 | + $values = array(); |
| 145 | + foreach ( array( 'total', 'translated', 'fuzzy' ) as $type ) { |
| 146 | + if ( !isset( $changes[$type] ) ) { |
| 147 | + $values[] = "tgs_$type=tgs_$type" . |
| 148 | + self::stringifyNumber( $changes[$type] ); |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + $dbw->update( self::TABLE, $values, $conds, __METHOD__ ); |
| 153 | + } |
| 154 | + |
95 | 155 | /** |
96 | 156 | * Returns an array of needed database fields. |
97 | 157 | */ |
— | — | @@ -162,7 +222,7 @@ |
163 | 223 | if ( !isset( $stats[$sid][$code] ) ) { |
164 | 224 | $stats[$sid][$code] = self::forItemInternal( $stats, $sgroup, $code ); |
165 | 225 | } |
166 | | - self::multiAdd( $aggregates, $stats[$sid][$code] ); |
| 226 | + $aggregates = self::multiAdd( $aggregates, $stats[$sid][$code] ); |
167 | 227 | } |
168 | 228 | $stats[$id] = $aggregates; |
169 | 229 | } else { |
— | — | @@ -235,34 +295,6 @@ |
236 | 296 | return array( $total, $translated, $fuzzy ); |
237 | 297 | } |
238 | 298 | |
239 | | - public static function update( MessageHandle $handle, $changes = array() ) { |
240 | | - $dbw = wfGetDB( DB_MASTER ); |
241 | | - $conds = array( |
242 | | - 'tgs_group' => $handle->getGroupIds(), |
243 | | - 'tgs_lang' => $handle->getCode(), |
244 | | - ); |
245 | | - |
246 | | - $values = array(); |
247 | | - foreach ( array( 'total', 'translated', 'fuzzy' ) as $type ) { |
248 | | - if ( !isset( $changes[$type] ) ) { |
249 | | - $values[] = "tgs_$type=tgs_$type" . |
250 | | - self::stringifyNumber( $changes[$type] ); |
251 | | - } |
252 | | - } |
253 | | - |
254 | | - $dbw->update( self::TABLE, $values, $conds, __METHOD__ ); |
255 | | - } |
256 | | - |
257 | | - public static function clear( MessageHandle $handle ) { |
258 | | - $dbw = wfGetDB( DB_MASTER ); |
259 | | - $conds = array( |
260 | | - 'tgs_group' => $handle->getGroupIds(), |
261 | | - 'tgs_lang' => $handle->getCode(), |
262 | | - ); |
263 | | - |
264 | | - $dbw->delete( self::TABLE, $conds, __METHOD__ ); |
265 | | - } |
266 | | - |
267 | 299 | /** |
268 | 300 | * Converts input to "+2" "-4" type of string. |
269 | 301 | * @param $number int |
Index: trunk/extensions/Translate/Translate.i18n.php |
— | — | @@ -220,6 +220,7 @@ |
221 | 221 | 'languagestats-summary' => 'This page shows translation statistics for all message groups for a language.', |
222 | 222 | 'languagestats-stats-for' => 'Translation statistics for $1 ($2).', |
223 | 223 | 'languagestats-recenttranslations' => 'recent translations', |
| 224 | + 'translate-langstats-incomplete' => 'Some of the statistics on this page are incomplete. Please reload to get more statistics.', |
224 | 225 | 'translate-langstats-expand' => 'expand', |
225 | 226 | 'translate-langstats-collapse' => 'collapse', |
226 | 227 | 'translate-langstats-expandall' => 'expand all', |
Index: trunk/extensions/Translate/specials/SpecialLanguageStats.php |
— | — | @@ -23,6 +23,8 @@ |
24 | 24 | class SpecialLanguageStats extends IncludableSpecialPage { |
25 | 25 | protected $purge = false; |
26 | 26 | |
| 27 | + protected $incomplete = false; |
| 28 | + |
27 | 29 | function __construct() { |
28 | 30 | parent::__construct( 'LanguageStats' ); |
29 | 31 | } |
— | — | @@ -52,10 +54,8 @@ |
53 | 55 | } |
54 | 56 | |
55 | 57 | if ( !$code ) { |
56 | | - |
57 | 58 | if ( $wgUser->isLoggedIn() ) { |
58 | 59 | global $wgLang; |
59 | | - |
60 | 60 | $code = $wgLang->getCode(); |
61 | 61 | } |
62 | 62 | } |
— | — | @@ -64,6 +64,9 @@ |
65 | 65 | |
66 | 66 | if ( array_key_exists( $code, Language::getLanguageNames() ) ) { |
67 | 67 | $out .= $this->getGroupStats( $code, $suppressComplete ); |
| 68 | + if ( $this->incomplete ) { |
| 69 | + $wgOut->wrapWikiMsg( "<div class='error'>$1</div>", 'translate-langstats-incomplete' ); |
| 70 | + } |
68 | 71 | } elseif ( $code ) { |
69 | 72 | $wgOut->wrapWikiMsg( "<div class='error'>$1</div>", 'translate-page-no-such-language' ); |
70 | 73 | } |
— | — | @@ -213,15 +216,18 @@ |
214 | 217 | |
215 | 218 | $out = ''; |
216 | 219 | |
217 | | - $cache = new ArrayMemoryCache( 'groupstats' ); |
| 220 | + if ( $this->purge ) { |
| 221 | + MessageGroupStats::clearLanguage( $code ); |
| 222 | + } |
| 223 | + |
| 224 | + MessageGroupStats::setTimeLimit( 8 ); |
| 225 | + $cache = MessageGroupStats::forLanguage( $code ); |
218 | 226 | $structure = MessageGroups::getGroupStructure(); |
219 | 227 | |
220 | 228 | foreach ( $structure as $item ) { |
221 | 229 | $out .= $this->makeGroupGroup( $item, $cache ); |
222 | 230 | } |
223 | 231 | |
224 | | - $cache->commit(); |
225 | | - |
226 | 232 | if ( $out ) { |
227 | 233 | $out = $this->createHeader( $code ) . "\n" . $out; |
228 | 234 | $out .= $this->makeTotalRow( $this->totals ); |
— | — | @@ -241,7 +247,7 @@ |
242 | 248 | } |
243 | 249 | |
244 | 250 | protected function makeTotalRow( $numbers ) { |
245 | | - list( $fuzzy, $translated, $total ) = $numbers; |
| 251 | + list( $total, $translated, $fuzzy ) = $numbers; |
246 | 252 | $out = "\t" . Html::openElement( 'tr' ); |
247 | 253 | $out .= "\n\t\t" . Html::rawElement( 'td', array(), wfMsg( 'translate-languagestats-overall' ) ); |
248 | 254 | $out .= $this->makeNumberColumns( $fuzzy, $translated, $total ); |
— | — | @@ -249,6 +255,14 @@ |
250 | 256 | } |
251 | 257 | |
252 | 258 | protected function makeNumberColumns( $fuzzy, $translated, $total ) { |
| 259 | + if ( $total === null ) { |
| 260 | + $na = "\n\t\t" . Html::element( 'td', array( 'data-sort-value' => -1 ), '...' ); |
| 261 | + $nap = "\n\t\t" . $this->element( '...', 'AFAFAF', -1 ); |
| 262 | + $out = $na . $na . $nap . $nap; |
| 263 | + $out .= "\n\t" . Xml::closeElement( 'tr' ) . "\n"; |
| 264 | + return $out; |
| 265 | + } |
| 266 | + |
253 | 267 | global $wgLang; |
254 | 268 | $out = "\n\t\t" . Html::element( 'td', |
255 | 269 | array( 'data-sort-value' => $total ), |
— | — | @@ -299,39 +313,36 @@ |
300 | 314 | return ''; |
301 | 315 | } |
302 | 316 | |
303 | | - $fuzzy = $translated = $total = 0; |
304 | | - |
305 | | - if ( $g instanceof AggregateMessageGroup ) { |
306 | | - foreach ( $g->getGroups() as $subgroup ) { |
307 | | - $result = $this->loadPercentages( $cache, $subgroup, $code ); |
308 | | - $fuzzy += $result[0]; |
309 | | - $translated += $result[1]; |
310 | | - $total += $result[2]; |
311 | | - } |
312 | | - } else { |
313 | | - list( $fuzzy, $translated, $total ) = $this->loadPercentages( $cache, $g, $code ); |
314 | | - $this->totals[2] += $total; |
315 | | - $this->totals[1] += $translated; |
316 | | - $this->totals[0] += $fuzzy; |
| 317 | + $stats = $cache[$groupId]; |
| 318 | + list( $total, $translated, $fuzzy ) = $stats; |
| 319 | + if ( !$group instanceof AggregateMessageGroup ) { |
| 320 | + $this->totals = MessageGroupStats::multiAdd( $this->totals, $stats ); |
317 | 321 | } |
| 322 | + if ( $total !== null ) { |
318 | 323 | |
319 | | - if ( $total == 0 ) { |
320 | | - $zero = serialize( $total ); |
321 | | - error_log( __METHOD__ . ": Group $groupName has zero message ($code): $zero" ); |
322 | | - return ''; |
323 | | - } |
324 | 324 | |
325 | | - // Skip if $suppressComplete and complete |
326 | | - if ( $suppressComplete && !$fuzzy && $translated === $total ) { |
327 | | - return ''; |
328 | | - } |
| 325 | + if ( $total == 0 ) { |
| 326 | + $zero = serialize( $total ); |
| 327 | + error_log( __METHOD__ . ": Group $groupName has zero message ($code): $zero" ); |
| 328 | + return ''; |
| 329 | + } |
329 | 330 | |
330 | | - if ( $translated === $total ) { |
331 | | - $extra = array( 'task' => 'reviewall' ); |
| 331 | + // Skip if $suppressComplete and complete |
| 332 | + if ( $suppressComplete && !$fuzzy && $translated === $total ) { |
| 333 | + return ''; |
| 334 | + } |
| 335 | + |
| 336 | + if ( $translated === $total ) { |
| 337 | + $extra = array( 'task' => 'reviewall' ); |
| 338 | + } else { |
| 339 | + $extra = array(); |
| 340 | + } |
332 | 341 | } else { |
333 | 342 | $extra = array(); |
| 343 | + $this->incomplete = true; |
334 | 344 | } |
335 | 345 | |
| 346 | + |
336 | 347 | $rowParams = array(); |
337 | 348 | $rowParams['data-groupid'] = $groupId; |
338 | 349 | if ( is_string( $parent ) ) { |
— | — | @@ -343,65 +354,10 @@ |
344 | 355 | $out .= "\t" . Html::openElement( 'tr', $rowParams ); |
345 | 356 | $out .= "\n\t\t" . Html::rawElement( 'td', array(), $this->makeGroupLink( $g, $code, $extra ) ); |
346 | 357 | $out .= $this->makeNumberColumns( $fuzzy, $translated, $total ); |
| 358 | + |
347 | 359 | return $out; |
348 | 360 | } |
349 | 361 | |
350 | | - protected function loadPercentages( $cache, $group, $code ) { |
351 | | - wfProfileIn( __METHOD__ ); |
352 | | - $id = $group->getId(); |
353 | | - |
354 | | - |
355 | | - $result = $cache->get( $id, $code ); |
356 | | - if ( !$this->purge && is_array( $result ) ) { |
357 | | - wfProfileOut( __METHOD__ ); |
358 | | - return $result; |
359 | | - } |
360 | | - |
361 | | - // Initialise messages. |
362 | | - $collection = $group->initCollection( $code ); |
363 | | - |
364 | | - $ffs = $group->getFFS(); |
365 | | - if ( $ffs instanceof GettextFFS && $code === 'qqq' ) { |
366 | | - $template = $ffs->read( 'en' ); |
367 | | - $infile = array(); |
368 | | - foreach ( $template['TEMPLATE'] as $key => $data ) { |
369 | | - if ( isset( $data['comments']['.'] ) ) { |
370 | | - $infile[$key] = '1'; |
371 | | - } |
372 | | - } |
373 | | - $collection->setInFile( $infile ); |
374 | | - } |
375 | | - |
376 | | - |
377 | | - // Takes too much memory and only hides inconsistent import state |
378 | | - # $collection->setInFile( $group->load( $code ) ); |
379 | | - $collection->filter( 'ignored' ); |
380 | | - $collection->filter( 'optional' ); |
381 | | - // Store the count of real messages for later calculation. |
382 | | - $total = count( $collection ); |
383 | | - |
384 | | - // Count fuzzy first. |
385 | | - $collection->filter( 'fuzzy' ); |
386 | | - $fuzzy = $total - count( $collection ); |
387 | | - |
388 | | - // Count the completed translations. |
389 | | - $collection->filter( 'hastranslation', false ); |
390 | | - $translated = count( $collection ); |
391 | | - |
392 | | - $result = array( $fuzzy, $translated, $total ); |
393 | | - |
394 | | - $cache->set( $id, $code, $result ); |
395 | | - |
396 | | - static $i = 0; |
397 | | - if ( $i++ % 50 === 0 ) { |
398 | | - $cache->commit(); |
399 | | - } |
400 | | - |
401 | | - wfProfileOut( __METHOD__ ); |
402 | | - |
403 | | - return $result; |
404 | | - } |
405 | | - |
406 | 362 | protected function formatPercentage( $num ) { |
407 | 363 | global $wgLang; |
408 | 364 | $fmt = $wgLang->formatNum( number_format( round( 100 * $num, 2 ), 2 ) ); |
— | — | @@ -439,15 +395,27 @@ |
440 | 396 | |
441 | 397 | protected function getGroupDescription( $group ) { |
442 | 398 | global $wgLang; |
| 399 | + $code = $wgLang->getCode(); |
| 400 | + |
| 401 | + $cache = wfGetCache( CACHE_ANYTHING ); |
| 402 | + $key = wfMemckey( "translate-groupdesc-$code-" . $group->getId() ); |
| 403 | + $desc = $cache->get( $key ); |
| 404 | + if ( is_string( $desc ) ) { |
| 405 | + return $desc; |
| 406 | + } |
443 | 407 | |
| 408 | + global $wgLang; |
| 409 | + |
444 | 410 | $realFunction = array( 'MessageCache', 'singleton' ); |
445 | 411 | if ( is_callable( $realFunction ) ) { |
446 | | - $cache = MessageCache::singleton(); |
| 412 | + $mc = MessageCache::singleton(); |
447 | 413 | } else { |
448 | 414 | global $wgMessageCache; |
449 | | - $cache = $wgMessageCache; |
| 415 | + $mc = $wgMessageCache; |
450 | 416 | } |
451 | | - return $cache->transform( $group->getDescription(), true, $wgLang, $this->getTitle() ); |
| 417 | + $desc = $mc->transform( $group->getDescription(), true, $wgLang, $this->getTitle() ); |
| 418 | + $cache->set( $key, $desc ); |
| 419 | + return $desc; |
452 | 420 | } |
453 | 421 | |
454 | 422 | protected function isBlacklisted( $groupId, $code ) { |