3 * Check a language file.
5 * @addtogroup Maintenance
8 require_once( dirname(__FILE__
).'/../commandLine.inc' );
9 require_once( 'languages.inc' );
11 $cli = new CheckLanguageCLI( $options );
14 class CheckLanguageCLI
{
17 private $doLinks = false;
18 private $wikiCode = 'en';
19 private $includeExif = false;
20 private $checkAll = false;
21 private $output = 'plain';
22 private $checks = array();
24 private $defaultChecks = array(
25 'untranslated', 'obsolete', 'variables', 'empty', 'plural',
26 'whitespace', 'xhtml', 'chars', 'links', 'unbalanced'
32 * GLOBALS: $wgLanguageCode;
34 public function __construct( Array $options ) {
36 if ( isset( $options['help'] ) ) {
41 if ( isset($options['lang']) ) {
42 $this->code
= $options['lang'];
44 global $wgLanguageCode;
45 $this->code
= $wgLanguageCode;
48 if ( isset($options['level']) ) {
49 $this->level
= $options['level'];
52 $this->doLinks
= isset($options['links']);
53 $this->includeExif
= !isset($options['noexif']);
54 $this->checkAll
= isset($options['all']);
56 if ( isset($options['wikilang']) ) {
57 $this->wikiCode
= $options['wikilang'];
60 if ( isset( $options['whitelist'] ) ) {
61 $this->checks
= explode( ',', $options['whitelist'] );
62 } elseif ( isset( $options['blacklist'] ) ) {
63 $this->checks
= array_diff(
65 explode( ',', $options['blacklist'] )
68 $this->checks
= $this->defaultChecks
;
71 if ( isset($options['output']) ) {
72 $this->output
= $options['output'];
75 # Some additional checks not enabled by default
76 if ( isset( $options['duplicate'] ) ) {
77 $this->checks
[] = 'duplicate';
80 $this->L
= new languages( $this->includeExif
);
83 protected function getChecks() {
85 $checks['untranslated'] = 'getUntranslatedMessages';
86 $checks['duplicate'] = 'getDuplicateMessages';
87 $checks['obsolete'] = 'getObsoleteMessages';
88 $checks['variables'] = 'getMessagesWithoutVariables';
89 $checks['plural'] = 'getMessagesWithoutPlural';
90 $checks['empty'] = 'getEmptyMessages';
91 $checks['whitespace'] = 'getMessagesWithWhitespace';
92 $checks['xhtml'] = 'getNonXHTMLMessages';
93 $checks['chars'] = 'getMessagesWithWrongChars';
94 $checks['links'] = 'getMessagesWithDubiousLinks';
95 $checks['unbalanced'] = 'getMessagesWithUnbalanced';
99 protected function getDescriptions() {
100 $descriptions = array();
101 $descriptions['untranslated'] = '$1 message(s) of $2 are not translated to $3, but exist in en:';
102 $descriptions['duplicate'] = '$1 message(s) of $2 are translated the same in en and $3:';
103 $descriptions['obsolete'] = '$1 message(s) of $2 do not exist in en or are in the ignore list, but are in $3';
104 $descriptions['variables'] = '$1 message(s) of $2 in $3 don\'t use some variables that en uses:';
105 $descriptions['plural'] = '$1 message(s) of $2 in $3 don\'t use {{plural}} while en uses:';
106 $descriptions['empty'] = '$1 message(s) of $2 in $3 are empty or -:';
107 $descriptions['whitespace'] = '$1 message(s) of $2 in $3 have trailing whitespace:';
108 $descriptions['xhtml'] = '$1 message(s) of $2 in $3 contain illegal XHTML:';
109 $descriptions['chars'] = '$1 message(s) of $2 in $3 include hidden chars which should not be used in the messages:';
110 $descriptions['links'] = '$1 message(s) of $2 in $3 have problematic link(s):';
111 $descriptions['unbalanced'] = '$1 message(s) of $2 in $3 have unbalanced {[]}:';
112 return $descriptions;
115 protected function help() {
117 Run this script to check a specific language file, or all of them.
118 Command line settings are in form --parameter[=value].
120 * lang: Language code (default: the installation default language).
121 * all: Check all customized languages
122 * help: Show this help.
123 * level: Show the following level (default: 2).
124 * links: Link the message values (default off).
125 * wikilang: For the links, what is the content language of the wiki to display the output in (default en).
126 * whitelist: Do only the following checks (form: code,code).
127 * blacklist: Don't do the following checks (form: code,code).
128 * duplicate: Additionally check for messages which are translated the same to English (default off).
129 * noexif: Don't check for EXIF messages (a bit hard and boring to translate), if you know that they are currently not translated and want to focus on other problems (default off).
130 Check codes (ideally, all of them should result 0; all the checks are executed by default (except duplicate and language specific check blacklists in checkLanguage.inc):
131 * untranslated: Messages which are required to translate, but are not translated.
132 * duplicate: Messages which translation equal to fallback
133 * obsolete: Messages which are untranslatable, but translated.
134 * variables: Messages without variables which should be used.
135 * empty: Empty messages.
136 * whitespace: Messages which have trailing whitespace.
137 * xhtml: Messages which are not well-formed XHTML (checks only few common errors).
138 * chars: Messages with hidden characters.
139 * links: Messages which contains broken links to pages (does not find all).
140 * unbalanced: Messages which contains unequal numbers of opening {[ and closing ]}.
141 Display levels (default: 2):
142 * 0: Skip the checks (useful for checking syntax).
143 * 1: Show only the stub headers and number of wrong messages, without list of messages.
144 * 2: Show only the headers and the message keys, without the message values.
145 * 3: Show both the headers and the complete messages, with both keys and values.
150 private $results = array();
152 public function execute() {
154 if ( $this->level
> 0 ) {
155 switch ($this->output
) {
163 throw new MWException( "Invalid output type $this->output");
168 protected function doChecks() {
169 $ignoredCodes = array( 'en', 'enRTL' );
171 $this->results
= array();
173 if ( $this->checkAll
) {
174 foreach ( $this->L
->getLanguages() as $language ) {
175 if ( !in_array($this->code
, $ignoredCodes) ) {
176 $this->results
[$language] = $this->checkLanguage( $language );
180 if ( in_array($this->code
, $ignoredCodes) ) {
181 throw new MWException("Cannot check code $this->code.");
183 $this->results
[$this->code
] = $this->checkLanguage( $this->code
);
188 protected function getCheckBlacklist() {
189 static $checkBlacklist = null;
190 if ( $checkBlacklist === null ) {
191 $checkBlacklist = array();
192 require( dirname(__FILE__
) . '/checkLanguage.inc' );
194 return $checkBlacklist;
197 protected function checkLanguage( $code ) {
199 if ( $this->level
=== 0 ) {
200 $this->L
->getMessages( $code );
205 $checkFunctions = $this->getChecks();
206 $checkBlacklist = $this->getCheckBlacklist();
207 foreach ( $this->checks
as $check ) {
208 if ( isset($checkBlacklist[$code]) &&
209 in_array($check, $checkBlacklist[$code]) ) {
210 $result[$check] = array();
214 $callback = array( $this->L
, $checkFunctions[$check] );
215 if ( !is_callable($callback ) ) {
216 throw new MWException( "Unkown check $check." );
218 $results[$check] = call_user_func( $callback , $code );
224 protected function outputText( ) {
225 foreach ( $this->results
as $code => $results ) {
226 $translated = $this->L
->getMessages( $code );
227 $translated = count( $translated['translated'] );
228 foreach ( $results as $check => $messages ) {
229 $count = count( $messages );
231 $search = array( '$1', '$2', '$3' );
232 $replace = array( $count, $translated, $code );
233 $descriptions = $this->getDescriptions();
234 echo "\n" . str_replace( $search, $replace, $descriptions[$check] ) . "\n";
235 if ( $this->level
== 1 ) {
236 echo "[messages are hidden]\n";
238 foreach ( $messages as $key => $value ) {
239 if ( $this->doLinks
) {
240 $displayKey = ucfirst( $key );
241 if ( $code == $this->wikiCode
) {
242 $displayKey = "[[MediaWiki:$displayKey|$key]]";
244 $displayKey = "[[MediaWiki:$displayKey/$code|$key]]";
249 if ( $this->level
== 2 ) {
250 echo "* $displayKey\n";
252 echo "* $displayKey: '$value'\n";
262 * Globals: $wgContLang, $IP
264 function outputWiki() {
265 global $wgContLang, $IP;
267 $rows[] = '! Language !! Code !! Total !! ' . implode( ' !! ', $this->checks
);
268 foreach ( $this->results
as $code => $results ) {
269 $detailTextForLang = "==$code==\n";
272 $detailTextForLangChecks = array();
273 foreach ( $results as $check => $messages ) {
274 $count = count( $messages );
277 $messageDetails = array();
278 foreach ( $messages as $key => $details ) {
279 $messageDetails[] = $key;
281 $detailTextForLangChecks[] = "===$code-$check===\n* " . implode( ', ', $messageDetails );
282 $numbers[] = "'''[[#$code-$check|$count]]'''";
289 if ( count( $detailTextForLangChecks ) ) {
290 $detailText .= $detailTextForLang . implode( "\n", $detailTextForLangChecks ) . "\n";
293 $language = $wgContLang->getLanguageName( $code );
294 $rows[] = "| $language || $code || $problems || " . implode( ' || ', $numbers );
297 $tableRows = implode( "\n|-\n", $rows );
299 $version = SpecialVersion
::getVersion( $IP );
301 '''Check results are for:''' <code>$version</code>
304 {| class="sortable wikitable" border="2" cellpadding="4" cellspacing="0" style="background-color: #F9F9F9; border: 1px #AAAAAA solid; border-collapse: collapse; clear:both;"