Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
StatsTable.php
1<?php
2declare( strict_types=1 );
3
4namespace MediaWiki\Extension\Translate\Statistics;
5
6use Html;
7use HtmlArmor;
8use Language;
10use MediaWiki\Linker\LinkRenderer;
11use Message;
12use MessageGroup;
15use MessageLocalizer;
16use SpecialPage;
17use TitleValue;
19use Xml;
20
29 protected $translate;
31 private $linkRenderer;
33 private $configHelper;
35 private $messageLocalizer;
37 protected $language;
39 protected $mainColumnHeader;
41 protected $extraColumns = [];
42
43 public function __construct(
44 LinkRenderer $linkRenderer,
45 ConfigHelper $configHelper,
46 MessageLocalizer $messageLocalizer,
47 Language $language
48 ) {
49 $this->translate = SpecialPage::getTitleValueFor( 'Translate' );
50 $this->linkRenderer = $linkRenderer;
51 $this->configHelper = $configHelper;
52 $this->messageLocalizer = $messageLocalizer;
53 $this->language = $language;
54 }
55
63 public function element( string $in, string $bgcolor = '', string $sort = '' ): string {
64 $attributes = [];
65
66 if ( $sort ) {
67 $attributes['data-sort-value'] = $sort;
68 }
69
70 if ( $bgcolor ) {
71 $attributes['style'] = 'background-color: #' . $bgcolor;
72 }
73
74 $element = Html::element( 'td', $attributes, $in );
75
76 return $element;
77 }
78
79 public function getBackgroundColor( float $percentage, bool $fuzzy = false ): string {
80 if ( $fuzzy ) {
81 // Steeper scale for fuzzy
82 // (0), [0-2), [2-4), ... [12-100)
83 $index = min( 7, ceil( 50 * $percentage ) );
84 $colors = [
85 '', 'fedbd7', 'fecec8', 'fec1b9',
86 'fcb5ab', 'fba89d', 'f89b8f', 'f68d81'
87 ];
88 return $colors[ $index ];
89 }
90
91 // https://gka.github.io/palettes/#colors=#36c,#eaf3ff|steps=20|bez=1|coL=1
92 // Color groups for (0-10], (10-20], ... (90-100], (100)
93 $index = floor( $percentage * 10 );
94 $colors = [
95 'eaf3ff', 'e2ebfc', 'dae3fa', 'd2dbf7', 'c9d4f5',
96 'c1ccf2', 'b8c4ef', 'b1bced', 'a8b4ea', '9fade8',
97 '96a6e5'
98 ];
99
100 return $colors[ $index ];
101 }
102
103 private function getMainColumnHeader(): string {
104 return $this->mainColumnHeader;
105 }
106
107 public function setMainColumnHeader( Message $msg ): void {
108 $this->mainColumnHeader = $this->createColumnHeader( $msg );
109 }
110
112 private function createColumnHeader( Message $msg ): string {
113 return Html::element( 'th', [], $msg->text() );
114 }
115
116 public function addExtraColumn( Message $column ): void {
117 $this->extraColumns[] = $column;
118 }
119
121 private function getOtherColumnHeaders(): array {
122 return array_merge( [
123 $this->messageLocalizer->msg( 'translate-total' ),
124 $this->messageLocalizer->msg( 'translate-untranslated' ),
125 $this->messageLocalizer->msg( 'translate-percentage-complete' ),
126 $this->messageLocalizer->msg( 'translate-percentage-proofread' ),
127 $this->messageLocalizer->msg( 'translate-percentage-fuzzy' ),
128 ], $this->extraColumns );
129 }
130
132 public function createHeader(): string {
133 // Create table header
134 $out = Html::openElement(
135 'table',
136 [ 'class' => 'statstable' ]
137 );
138
139 $out .= "\n\t" . Html::openElement( 'thead' );
140 $out .= "\n\t" . Html::openElement( 'tr' );
141
142 $out .= "\n\t\t" . $this->getMainColumnHeader();
143 foreach ( $this->getOtherColumnHeaders() as $label ) {
144 $out .= "\n\t\t" . $this->createColumnHeader( $label );
145 }
146 $out .= "\n\t" . Html::closeElement( 'tr' );
147 $out .= "\n\t" . Html::closeElement( 'thead' );
148 $out .= "\n\t" . Html::openElement( 'tbody' );
149
150 return $out;
151 }
152
159 public function makeTotalRow( Message $message, array $stats ): string {
160 $out = "\t" . Html::openElement( 'tr' );
161 $out .= "\n\t\t" . Html::element( 'td', [], $message->text() );
162 $out .= $this->makeNumberColumns( $stats );
163 $out .= "\n\t" . Xml::closeElement( 'tr' ) . "\n";
164
165 return $out;
166 }
167
172 public function makeNumberColumns( array $stats ): string {
173 $total = $stats[MessageGroupStats::TOTAL];
174 $translated = $stats[MessageGroupStats::TRANSLATED];
175 $fuzzy = $stats[MessageGroupStats::FUZZY];
176 $proofread = $stats[MessageGroupStats::PROOFREAD];
177
178 if ( $total === null ) {
179 $na = "\n\t\t" . Html::element( 'td', [ 'data-sort-value' => -1 ], '...' );
180 $nap = "\n\t\t" . $this->element( '...', 'AFAFAF', '-1' );
181 $out = $na . $na . $nap . $nap;
182
183 return $out;
184 }
185
186 $out = "\n\t\t" . Html::element( 'td',
187 [ 'data-sort-value' => $total ],
188 $this->language->formatNum( $total ) );
189
190 $out .= "\n\t\t" . Html::element( 'td',
191 [ 'data-sort-value' => $total - $translated ],
192 $this->language->formatNum( $total - $translated ) );
193
194 if ( $total === 0 ) {
195 $transRatio = 0;
196 $fuzzyRatio = 0;
197 $proofRatio = 0;
198 } else {
199 $transRatio = $translated / $total;
200 $fuzzyRatio = $fuzzy / $total;
201 $proofRatio = $translated === 0 ? 0 : $proofread / $translated;
202 }
203
204 $out .= "\n\t\t" . $this->element( $this->formatPercentage( $transRatio, 'floor' ),
205 $this->getBackgroundColor( $transRatio ),
206 sprintf( '%1.5f', $transRatio ) );
207
208 $out .= "\n\t\t" . $this->element( $this->formatPercentage( $proofRatio, 'floor' ),
209 $this->getBackgroundColor( $proofRatio ),
210 sprintf( '%1.5f', $proofRatio ) );
211
212 $out .= "\n\t\t" . $this->element( $this->formatPercentage( $fuzzyRatio, 'ceil' ),
213 $this->getBackgroundColor( $fuzzyRatio, true ),
214 sprintf( '%1.5f', $fuzzyRatio ) );
215
216 return $out;
217 }
218
219 public function makeWorkflowStateCell( ?string $state, MessageGroup $group, string $language ): string {
220 if ( $state === null ) {
221 return "\n\t\t" . $this->element( '', '', '-1' );
222 }
223
224 if ( $group->getSourceLanguage() === $language ) {
225 return "\n\t\t" . $this->element( '', '', '-1' );
226 }
227
228 $stateConfig = $group->getMessageGroupStates()->getStates();
229 $sortValue = '-1';
230 $stateColor = '';
231 if ( isset( $stateConfig[$state] ) ) {
232 $sortIndex = array_flip( array_keys( $stateConfig ) );
233 $sortValue = $sortIndex[$state] + 1;
234
235 if ( is_string( $stateConfig[$state] ) ) {
236 // BC for old configuration format
237 $stateColor = $stateConfig[$state];
238 } elseif ( isset( $stateConfig[$state]['color'] ) ) {
239 $stateColor = $stateConfig[$state]['color'];
240 }
241 }
242
243 $stateMessage = $this->messageLocalizer->msg( "translate-workflow-state-$state" );
244 $stateText = $stateMessage->isBlank() ? $state : $stateMessage->text();
245
246 return "\n\t\t" . $this->element(
247 $stateText,
248 $stateColor,
249 (string)$sortValue
250 );
251 }
252
259 public function formatPercentage( $num, string $to = 'floor' ): string {
260 $num = $to === 'floor' ? floor( 100 * $num ) : ceil( 100 * $num );
261 $fmt = $this->language->formatNum( $num );
262
263 return $this->messageLocalizer->msg( 'percent', $fmt )->text();
264 }
265
270 private function getGroupLabel( MessageGroup $group ): string {
271 $groupLabel = htmlspecialchars( $group->getLabel() );
272
273 // Bold for meta groups.
274 if ( $group->isMeta() ) {
275 $groupLabel = Html::rawElement( 'b', [], $groupLabel );
276 }
277
278 return $groupLabel;
279 }
280
288 public function makeGroupLink( MessageGroup $group, string $code, array $params ): string {
289 $queryParameters = $params + [
290 'group' => $group->getId(),
291 'language' => $code
292 ];
293
294 return $this->linkRenderer->makeLink(
295 $this->translate,
296 new HtmlArmor( $this->getGroupLabel( $group ) ),
297 [],
298 $queryParameters
299 );
300 }
301
308 public function isExcluded( string $groupId, string $code ): bool {
309 $excluded = null;
310
311 $checks = [
312 $groupId,
313 strtok( $groupId, '-' ),
314 '*'
315 ];
316
317 $disabledLanguages = $this->configHelper->getDisabledTargetLanguages();
318
319 foreach ( $checks as $check ) {
320 if ( isset( $disabledLanguages[$check] ) && isset( $disabledLanguages[$check][$code] ) ) {
321 $excluded = $disabledLanguages[$check][$code];
322 }
323
324 if ( $excluded !== null ) {
325 break;
326 }
327 }
328
329 $group = MessageGroups::getGroup( $groupId );
330 $languages = $group->getTranslatableLanguages();
331 if ( $languages !== null && !isset( $languages[$code] ) ) {
332 $excluded = true;
333 }
334
335 if ( TranslateMetadata::isExcluded( $groupId, $code ) ) {
336 $excluded = true;
337 }
338
339 return (bool)$excluded;
340 }
341}
return[ 'Translate:ConfigHelper'=> static function():ConfigHelper { return new ConfigHelper();}, 'Translate:CsvTranslationImporter'=> static function(MediaWikiServices $services):CsvTranslationImporter { return new CsvTranslationImporter( $services->getWikiPageFactory());}, 'Translate:EntitySearch'=> static function(MediaWikiServices $services):EntitySearch { return new EntitySearch($services->getMainWANObjectCache(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), MessageGroups::singleton(), $services->getNamespaceInfo(), $services->get( 'Translate:MessageIndex'), $services->getTitleParser(), $services->getTitleFormatter());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->getMainConfig(), $services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance( 'Translate.GroupSynchronization'), MessageIndex::singleton());}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore(new RevTagStore(), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, 'Translate:MessageGroupReview'=> static function(MediaWikiServices $services):MessageGroupReview { return new MessageGroupReview($services->getDBLoadBalancer(), $services->getHookContainer());}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=$services->getMainConfig() ->get( 'TranslateMessageIndex');if(is_string( $params)) { $params=(array) $params;} $class=array_shift( $params);return new $class( $params);}, 'Translate:ParsingPlaceholderFactory'=> static function():ParsingPlaceholderFactory { return new ParsingPlaceholderFactory();}, 'Translate:PersistentCache'=> static function(MediaWikiServices $services):PersistentCache { return new PersistentDatabaseCache($services->getDBLoadBalancer(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'));}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleMover'=> static function(MediaWikiServices $services):TranslatableBundleMover { return new TranslatableBundleMover($services->getMovePageFactory(), $services->getJobQueueGroup(), $services->getLinkBatchFactory(), $services->get( 'Translate:TranslatableBundleFactory'), $services->get( 'Translate:SubpageListBuilder'), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), new RevTagStore(), $services->getDBLoadBalancer());}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnectionRef(DB_REPLICA);return new TranslationStashStorage( $db);}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory());}, 'Translate:TranslationUnitStoreFactory'=> static function(MediaWikiServices $services):TranslationUnitStoreFactory { return new TranslationUnitStoreFactory( $services->getDBLoadBalancer());}, 'Translate:TranslatorActivity'=> static function(MediaWikiServices $services):TranslatorActivity { $query=new TranslatorActivityQuery($services->getMainConfig(), $services->getDBLoadBalancer());return new TranslatorActivity($services->getMainObjectStash(), $query, $services->getJobQueueGroup());}, 'Translate:TtmServerFactory'=> static function(MediaWikiServices $services):TtmServerFactory { $config=$services->getMainConfig();$default=$config->get( 'TranslateTranslationDefaultService');if( $default===false) { $default=null;} return new TtmServerFactory( $config->get( 'TranslateTranslationServices'), $default);}]
@phpcs-require-sorted-array
Implements generation of HTML stats table.
makeTotalRow(Message $message, array $stats)
Makes a row with aggregate numbers.
element(string $in, string $bgcolor='', string $sort='')
Statistics table element (heading or regular cell)
isExcluded(string $groupId, string $code)
Check whether translations in given group in given language has been disabled.
formatPercentage( $num, string $to='floor')
Makes a nice print from plain float.
makeGroupLink(MessageGroup $group, string $code, array $params)
Gets the name of group linked to translation tool.
makeNumberColumns(array $stats)
Makes partial row from completion numbers.
A helper class added to work with configuration values of the Translate Extension.
This class abstract MessageGroup statistics calculation and storing.
Factory class for accessing message groups individually by id or all of them as an list.
Interface for message groups.
getTranslatableLanguages()
Get all the translatable languages for a group, considering the inclusion and exclusion list.