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;
14use MessageLocalizer;
15use SpecialPage;
16use TitleValue;
18use Xml;
19
27 protected TitleValue $translate;
28 private LinkRenderer $linkRenderer;
29 private ConfigHelper $configHelper;
30 private MessageLocalizer $messageLocalizer;
31 protected Language $language;
32 protected string $mainColumnHeader;
34 protected array $extraColumns = [];
35
36 public function __construct(
37 LinkRenderer $linkRenderer,
38 ConfigHelper $configHelper,
39 MessageLocalizer $messageLocalizer,
40 Language $language
41 ) {
42 $this->translate = SpecialPage::getTitleValueFor( 'Translate' );
43 $this->linkRenderer = $linkRenderer;
44 $this->configHelper = $configHelper;
45 $this->messageLocalizer = $messageLocalizer;
46 $this->language = $language;
47 }
48
56 public function element( string $in, string $bgcolor = '', string $sort = '' ): string {
57 $attributes = [];
58
59 if ( $sort ) {
60 $attributes['data-sort-value'] = $sort;
61 }
62
63 if ( $bgcolor ) {
64 $attributes['style'] = 'background-color: #' . $bgcolor;
65 }
66
67 $element = Html::element( 'td', $attributes, $in );
68
69 return $element;
70 }
71
72 public function getBackgroundColor( float $percentage, bool $fuzzy = false ): string {
73 if ( $fuzzy ) {
74 // Steeper scale for fuzzy
75 // (0), [0-2), [2-4), ... [12-100)
76 $index = min( 7, ceil( 50 * $percentage ) );
77 $colors = [
78 '', 'fedbd7', 'fecec8', 'fec1b9',
79 'fcb5ab', 'fba89d', 'f89b8f', 'f68d81'
80 ];
81 return $colors[ $index ];
82 }
83
84 // https://gka.github.io/palettes/#colors=#36c,#eaf3ff|steps=20|bez=1|coL=1
85 // Color groups for (0-10], (10-20], ... (90-100], (100)
86 $index = (int)floor( $percentage * 10 );
87 $colors = [
88 'eaf3ff', 'e2ebfc', 'dae3fa', 'd2dbf7', 'c9d4f5',
89 'c1ccf2', 'b8c4ef', 'b1bced', 'a8b4ea', '9fade8',
90 '96a6e5'
91 ];
92
93 return $colors[ $index ];
94 }
95
96 private function getMainColumnHeader(): string {
97 return $this->mainColumnHeader;
98 }
99
100 public function setMainColumnHeader( Message $msg ): void {
101 $this->mainColumnHeader = $this->createColumnHeader( $msg );
102 }
103
105 private function createColumnHeader( Message $msg ): string {
106 return Html::element( 'th', [], $msg->text() );
107 }
108
109 public function addExtraColumn( Message $column ): void {
110 $this->extraColumns[] = $column;
111 }
112
114 private function getOtherColumnHeaders(): array {
115 return array_merge( [
116 $this->messageLocalizer->msg( 'translate-total' ),
117 $this->messageLocalizer->msg( 'translate-untranslated' ),
118 $this->messageLocalizer->msg( 'translate-percentage-complete' ),
119 $this->messageLocalizer->msg( 'translate-percentage-proofread' ),
120 $this->messageLocalizer->msg( 'translate-percentage-fuzzy' ),
121 ], $this->extraColumns );
122 }
123
125 public function createHeader(): string {
126 // Create table header
127 $out = Html::openElement(
128 'table',
129 [ 'class' => 'statstable' ]
130 );
131
132 $out .= "\n\t" . Html::openElement( 'thead' );
133 $out .= "\n\t" . Html::openElement( 'tr' );
134
135 $out .= "\n\t\t" . $this->getMainColumnHeader();
136 foreach ( $this->getOtherColumnHeaders() as $label ) {
137 $out .= "\n\t\t" . $this->createColumnHeader( $label );
138 }
139 $out .= "\n\t" . Html::closeElement( 'tr' );
140 $out .= "\n\t" . Html::closeElement( 'thead' );
141 $out .= "\n\t" . Html::openElement( 'tbody' );
142
143 return $out;
144 }
145
152 public function makeTotalRow( Message $message, array $stats ): string {
153 $out = "\t" . Html::openElement( 'tr' );
154 $out .= "\n\t\t" . Html::element( 'td', [], $message->text() );
155 $out .= $this->makeNumberColumns( $stats );
156 $out .= "\n\t" . Xml::closeElement( 'tr' ) . "\n";
157
158 return $out;
159 }
160
165 public function makeNumberColumns( array $stats ): string {
166 $total = $stats[MessageGroupStats::TOTAL];
167 $translated = $stats[MessageGroupStats::TRANSLATED];
168 $fuzzy = $stats[MessageGroupStats::FUZZY];
169 $proofread = $stats[MessageGroupStats::PROOFREAD];
170
171 if ( $total === null ) {
172 $na = "\n\t\t" . Html::element( 'td', [ 'data-sort-value' => -1 ], '...' );
173 $nap = "\n\t\t" . $this->element( '...', 'AFAFAF', '-1' );
174 $out = $na . $na . $nap . $nap;
175
176 return $out;
177 }
178
179 $out = "\n\t\t" . Html::element( 'td',
180 [ 'data-sort-value' => $total ],
181 $this->language->formatNum( $total ) );
182
183 $out .= "\n\t\t" . Html::element( 'td',
184 [ 'data-sort-value' => $total - $translated ],
185 $this->language->formatNum( $total - $translated ) );
186
187 if ( $total === 0 ) {
188 $transRatio = 0;
189 $fuzzyRatio = 0;
190 $proofRatio = 0;
191 } else {
192 $transRatio = $translated / $total;
193 $fuzzyRatio = $fuzzy / $total;
194 $proofRatio = $translated ? $proofread / $translated : 0;
195 }
196
197 $out .= "\n\t\t" . $this->element( $this->formatPercentage( $transRatio, 'floor' ),
198 $this->getBackgroundColor( $transRatio ),
199 sprintf( '%1.5f', $transRatio ) );
200
201 $out .= "\n\t\t" . $this->element( $this->formatPercentage( $proofRatio, 'floor' ),
202 $this->getBackgroundColor( $proofRatio ),
203 sprintf( '%1.5f', $proofRatio ) );
204
205 $out .= "\n\t\t" . $this->element( $this->formatPercentage( $fuzzyRatio, 'ceil' ),
206 $this->getBackgroundColor( $fuzzyRatio, true ),
207 sprintf( '%1.5f', $fuzzyRatio ) );
208
209 return $out;
210 }
211
212 public function makeWorkflowStateCell( ?string $state, MessageGroup $group, string $language ): string {
213 if ( $state === null || $group->getSourceLanguage() === $language ) {
214 return "\n\t\t" . $this->element( '', '', '-1' );
215 }
216
217 $stateConfig = $group->getMessageGroupStates()->getStates();
218 $sortValue = '-1';
219 $stateColor = '';
220 if ( isset( $stateConfig[$state] ) ) {
221 $sortIndex = array_flip( array_keys( $stateConfig ) );
222 $sortValue = $sortIndex[$state] + 1;
223
224 if ( is_string( $stateConfig[$state] ) ) {
225 // BC for old configuration format
226 $stateColor = $stateConfig[$state];
227 } elseif ( isset( $stateConfig[$state]['color'] ) ) {
228 $stateColor = $stateConfig[$state]['color'];
229 }
230 }
231
232 $stateMessage = $this->messageLocalizer->msg( "translate-workflow-state-$state" );
233 $stateText = $stateMessage->isBlank() ? $state : $stateMessage->text();
234
235 return "\n\t\t" . $this->element(
236 $stateText,
237 $stateColor,
238 (string)$sortValue
239 );
240 }
241
248 public function formatPercentage( $num, string $to = 'floor' ): string {
249 $num = $to === 'floor' ? floor( 100 * $num ) : ceil( 100 * $num );
250 $fmt = $this->language->formatNum( $num );
251
252 return $this->messageLocalizer->msg( 'percent', $fmt )->text();
253 }
254
259 private function getGroupLabel( MessageGroup $group ): string {
260 $groupLabel = htmlspecialchars( $group->getLabel() );
261
262 // Bold for meta groups.
263 if ( $group->isMeta() ) {
264 $groupLabel = Html::rawElement( 'b', [], $groupLabel );
265 }
266
267 return $groupLabel;
268 }
269
277 public function makeGroupLink( MessageGroup $group, string $code, array $params ): string {
278 $queryParameters = $params + [
279 'group' => $group->getId(),
280 'language' => $code
281 ];
282
283 return $this->linkRenderer->makeLink(
284 $this->translate,
285 new HtmlArmor( $this->getGroupLabel( $group ) ),
286 [],
287 $queryParameters
288 );
289 }
290
297 public function isExcluded( MessageGroup $group, string $code ): bool {
298 $excluded = null;
299 $groupId = $group->getId();
300
301 $checks = [
302 $groupId,
303 strtok( $groupId, '-' ),
304 '*'
305 ];
306
307 $disabledLanguages = $this->configHelper->getDisabledTargetLanguages();
308
309 foreach ( $checks as $check ) {
310 if ( isset( $disabledLanguages[$check] ) && isset( $disabledLanguages[$check][$code] ) ) {
311 $excluded = $disabledLanguages[$check][$code];
312 }
313
314 if ( $excluded !== null ) {
315 break;
316 }
317 }
318
319 $languages = $group->getTranslatableLanguages();
320 if ( $languages !== null && !isset( $languages[$code] ) ) {
321 $excluded = true;
322 }
323
324 if ( TranslateMetadata::isExcluded( $groupId, $code ) ) {
325 $excluded = true;
326 }
327
328 return (bool)$excluded;
329 }
330}
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'), $services->get( 'Translate:MessageIndex'));}, 'Translate:FileFormatFactory'=> static function(MediaWikiServices $services):FileFormatFactory { return new FileFormatFactory( $services->getObjectFactory());}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getDBLoadBalancer(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getDBLoadBalancer(), $services->getLinkRenderer(), $services->get( 'Translate:MessageGroupReviewStore'), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, '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:MessagePrefixStats'=> static function(MediaWikiServices $services):MessagePrefixStats { return new MessagePrefixStats( $services->getTitleParser());}, '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:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore($services->getDBLoadBalancerFactory());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleExporter'=> static function(MediaWikiServices $services):TranslatableBundleExporter { return new TranslatableBundleExporter($services->get( 'Translate:SubpageListBuilder'), $services->getWikiExporterFactory(), $services->getDBLoadBalancer());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleImporter'=> static function(MediaWikiServices $services):TranslatableBundleImporter { return new TranslatableBundleImporter($services->getWikiImporterFactory(), $services->get( 'Translate:TranslatablePageParser'), $services->getRevisionLookup());}, '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:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getDBLoadBalancer() ->getConnection(DB_PRIMARY), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, '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(), $services->get( 'Translate:RevTagStore'), $services->getDBLoadBalancer(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'),);}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnection(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(), $services->getDBLoadBalancer());}, '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.
isExcluded(MessageGroup $group, string $code)
Check whether translations in given group in given language has been disabled.
element(string $in, string $bgcolor='', string $sort='')
Statistics table element (heading or regular cell)
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.
Interface for message groups.
getTranslatableLanguages()
Get all the translatable languages for a group, considering the inclusion and exclusion list.
getId()
Returns the unique identifier for this group.