MediaWiki
REL1_23
|
00001 <?php 00027 class CheckLanguageCLI { 00028 protected $code = null; 00029 protected $level = 2; 00030 protected $doLinks = false; 00031 protected $linksPrefix = ''; 00032 protected $wikiCode = 'en'; 00033 protected $checkAll = false; 00034 protected $output = 'plain'; 00035 protected $checks = array(); 00036 protected $L = null; 00037 00038 protected $results = array(); 00039 00040 private $includeExif = false; 00041 00046 public function __construct( array $options ) { 00047 if ( isset( $options['help'] ) ) { 00048 echo $this->help(); 00049 exit( 1 ); 00050 } 00051 00052 if ( isset( $options['lang'] ) ) { 00053 $this->code = $options['lang']; 00054 } else { 00055 global $wgLanguageCode; 00056 $this->code = $wgLanguageCode; 00057 } 00058 00059 if ( isset( $options['level'] ) ) { 00060 $this->level = $options['level']; 00061 } 00062 00063 $this->doLinks = isset( $options['links'] ); 00064 $this->includeExif = !isset( $options['noexif'] ); 00065 $this->checkAll = isset( $options['all'] ); 00066 00067 if ( isset( $options['prefix'] ) ) { 00068 $this->linksPrefix = $options['prefix']; 00069 } 00070 00071 if ( isset( $options['wikilang'] ) ) { 00072 $this->wikiCode = $options['wikilang']; 00073 } 00074 00075 if ( isset( $options['whitelist'] ) ) { 00076 $this->checks = explode( ',', $options['whitelist'] ); 00077 } elseif ( isset( $options['blacklist'] ) ) { 00078 $this->checks = array_diff( 00079 isset( $options['easy'] ) ? $this->easyChecks() : $this->defaultChecks(), 00080 explode( ',', $options['blacklist'] ) 00081 ); 00082 } elseif ( isset( $options['easy'] ) ) { 00083 $this->checks = $this->easyChecks(); 00084 } else { 00085 $this->checks = $this->defaultChecks(); 00086 } 00087 00088 if ( isset( $options['output'] ) ) { 00089 $this->output = $options['output']; 00090 } 00091 00092 $this->L = new Languages( $this->includeExif ); 00093 } 00094 00099 protected function defaultChecks() { 00100 return array( 00101 'untranslated', 'duplicate', 'obsolete', 'variables', 'empty', 'plural', 00102 'whitespace', 'xhtml', 'chars', 'links', 'unbalanced', 'namespace', 00103 'projecttalk', 'magic', 'magic-old', 'magic-over', 'magic-case', 00104 'special', 'special-old', 00105 ); 00106 } 00107 00112 protected function nonMessageChecks() { 00113 return array( 00114 'namespace', 'projecttalk', 'magic', 'magic-old', 'magic-over', 00115 'magic-case', 'special', 'special-old', 00116 ); 00117 } 00118 00123 protected function easyChecks() { 00124 return array( 00125 'duplicate', 'obsolete', 'empty', 'whitespace', 'xhtml', 'chars', 'magic-old', 00126 'magic-over', 'magic-case', 'special-old', 00127 ); 00128 } 00129 00134 protected function getChecks() { 00135 return array( 00136 'untranslated' => 'getUntranslatedMessages', 00137 'duplicate' => 'getDuplicateMessages', 00138 'obsolete' => 'getObsoleteMessages', 00139 'variables' => 'getMessagesWithMismatchVariables', 00140 'plural' => 'getMessagesWithoutPlural', 00141 'empty' => 'getEmptyMessages', 00142 'whitespace' => 'getMessagesWithWhitespace', 00143 'xhtml' => 'getNonXHTMLMessages', 00144 'chars' => 'getMessagesWithWrongChars', 00145 'links' => 'getMessagesWithDubiousLinks', 00146 'unbalanced' => 'getMessagesWithUnbalanced', 00147 'namespace' => 'getUntranslatedNamespaces', 00148 'projecttalk' => 'getProblematicProjectTalks', 00149 'magic' => 'getUntranslatedMagicWords', 00150 'magic-old' => 'getObsoleteMagicWords', 00151 'magic-over' => 'getOverridingMagicWords', 00152 'magic-case' => 'getCaseMismatchMagicWords', 00153 'special' => 'getUntraslatedSpecialPages', 00154 'special-old' => 'getObsoleteSpecialPages', 00155 ); 00156 } 00157 00164 protected function getTotalCount() { 00165 return array( 00166 'namespace' => array( 'getNamespaceNames', 'en' ), 00167 'projecttalk' => null, 00168 'magic' => array( 'getMagicWords', 'en' ), 00169 'magic-old' => array( 'getMagicWords', null ), 00170 'magic-over' => array( 'getMagicWords', null ), 00171 'magic-case' => array( 'getMagicWords', null ), 00172 'special' => array( 'getSpecialPageAliases', 'en' ), 00173 'special-old' => array( 'getSpecialPageAliases', null ), 00174 ); 00175 } 00176 00181 protected function getDescriptions() { 00182 return array( 00183 'untranslated' => '$1 message(s) of $2 are not translated to $3, but exist in en:', 00184 'duplicate' => '$1 message(s) of $2 are translated the same in en and $3:', 00185 'obsolete' => 00186 '$1 message(s) of $2 do not exist in en or are in the ignore list, but exist in $3:', 00187 'variables' => '$1 message(s) of $2 in $3 don\'t match the variables used in en:', 00188 'plural' => '$1 message(s) of $2 in $3 don\'t use {{plural}} while en uses:', 00189 'empty' => '$1 message(s) of $2 in $3 are empty or -:', 00190 'whitespace' => '$1 message(s) of $2 in $3 have trailing whitespace:', 00191 'xhtml' => '$1 message(s) of $2 in $3 contain illegal XHTML:', 00192 'chars' => 00193 '$1 message(s) of $2 in $3 include hidden chars which should not be used in the messages:', 00194 'links' => '$1 message(s) of $2 in $3 have problematic link(s):', 00195 'unbalanced' => '$1 message(s) of $2 in $3 have unbalanced {[]}:', 00196 'namespace' => '$1 namespace name(s) of $2 are not translated to $3, but exist in en:', 00197 'projecttalk' => 00198 '$1 namespace name(s) and alias(es) in $3 are project talk namespaces without the parameter:', 00199 'magic' => '$1 magic word(s) of $2 are not translated to $3, but exist in en:', 00200 'magic-old' => '$1 magic word(s) of $2 do not exist in en, but exist in $3:', 00201 'magic-over' => '$1 magic word(s) of $2 in $3 do not contain the original en word(s):', 00202 'magic-case' => 00203 '$1 magic word(s) of $2 in $3 change the case-sensitivity of the original en word:', 00204 'special' => '$1 special page alias(es) of $2 are not translated to $3, but exist in en:', 00205 'special-old' => '$1 special page alias(es) of $2 do not exist in en, but exist in $3:', 00206 ); 00207 } 00208 00213 protected function help() { 00214 return <<<ENDS 00215 Run this script to check a specific language file, or all of them. 00216 Command line settings are in form --parameter[=value]. 00217 Parameters: 00218 --help: Show this help. 00219 --lang: Language code (default: the installation default language). 00220 --all: Check all customized languages. 00221 --level: Show the following display level (default: 2): 00222 * 0: Skip the checks (useful for checking syntax). 00223 * 1: Show only the stub headers and number of wrong messages, without 00224 list of messages. 00225 * 2: Show only the headers and the message keys, without the message 00226 values. 00227 * 3: Show both the headers and the complete messages, with both keys and 00228 values. 00229 --links: Link the message values (default off). 00230 --prefix: prefix to add to links. 00231 --wikilang: For the links, what is the content language of the wiki to 00232 display the output in (default en). 00233 --noexif: Do not check for Exif messages (a bit hard and boring to 00234 translate), if you know what they are currently not translated and want 00235 to focus on other problems (default off). 00236 --whitelist: Do only the following checks (form: code,code). 00237 --blacklist: Do not do the following checks (form: code,code). 00238 --easy: Do only the easy checks, which can be treated by non-speakers of 00239 the language. 00240 00241 Check codes (ideally, all of them should result 0; all the checks are executed 00242 by default (except language-specific check blacklists in checkLanguage.inc): 00243 * untranslated: Messages which are required to translate, but are not 00244 translated. 00245 * duplicate: Messages which translation equal to fallback. 00246 * obsolete: Messages which are untranslatable or do not exist, but are 00247 translated. 00248 * variables: Messages without variables which should be used, or with 00249 variables which should not be used. 00250 * empty: Empty messages and messages that contain only -. 00251 * whitespace: Messages which have trailing whitespace. 00252 * xhtml: Messages which are not well-formed XHTML (checks only few common 00253 errors). 00254 * chars: Messages with hidden characters. 00255 * links: Messages which contains broken links to pages (does not find all). 00256 * unbalanced: Messages which contains unequal numbers of opening {[ and 00257 closing ]}. 00258 * namespace: Namespace names that were not translated. 00259 * projecttalk: Namespace names and aliases where the project talk does not 00260 contain $1. 00261 * magic: Magic words that were not translated. 00262 * magic-old: Magic words which do not exist. 00263 * magic-over: Magic words that override the original English word. 00264 * magic-case: Magic words whose translation changes the case-sensitivity of 00265 the original English word. 00266 * special: Special page names that were not translated. 00267 * special-old: Special page names which do not exist. 00268 00269 ENDS; 00270 } 00271 00275 public function execute() { 00276 $this->doChecks(); 00277 if ( $this->level > 0 ) { 00278 switch ( $this->output ) { 00279 case 'plain': 00280 $this->outputText(); 00281 break; 00282 case 'wiki': 00283 $this->outputWiki(); 00284 break; 00285 default: 00286 throw new MWException( "Invalid output type $this->output" ); 00287 } 00288 } 00289 } 00290 00294 protected function doChecks() { 00295 $ignoredCodes = array( 'en', 'enRTL' ); 00296 00297 $this->results = array(); 00298 # Check the language 00299 if ( $this->checkAll ) { 00300 foreach ( $this->L->getLanguages() as $language ) { 00301 if ( !in_array( $language, $ignoredCodes ) ) { 00302 $this->results[$language] = $this->checkLanguage( $language ); 00303 } 00304 } 00305 } else { 00306 if ( in_array( $this->code, $ignoredCodes ) ) { 00307 throw new MWException( "Cannot check code $this->code." ); 00308 } else { 00309 $this->results[$this->code] = $this->checkLanguage( $this->code ); 00310 } 00311 } 00312 00313 $results = $this->results; 00314 foreach ( $results as $code => $checks ) { 00315 foreach ( $checks as $check => $messages ) { 00316 foreach ( $messages as $key => $details ) { 00317 if ( $this->isCheckBlacklisted( $check, $code, $key ) ) { 00318 unset( $this->results[$code][$check][$key] ); 00319 } 00320 } 00321 } 00322 } 00323 } 00324 00329 protected function getCheckBlacklist() { 00330 static $blacklist = null; 00331 00332 if ( $blacklist !== null ) { 00333 return $blacklist; 00334 } 00335 00336 // @codingStandardsIgnoreStart Ignore that globals should have a "wg" prefix. 00337 global $checkBlacklist; 00338 // @codingStandardsIgnoreEnd 00339 00340 $blacklist = $checkBlacklist; 00341 00342 wfRunHooks( 'LocalisationChecksBlacklist', array( &$blacklist ) ); 00343 00344 return $blacklist; 00345 } 00346 00355 protected function isCheckBlacklisted( $check, $code, $message ) { 00356 $blacklist = $this->getCheckBlacklist(); 00357 00358 foreach ( $blacklist as $item ) { 00359 if ( isset( $item['check'] ) && $check !== $item['check'] ) { 00360 continue; 00361 } 00362 00363 if ( isset( $item['code'] ) && !in_array( $code, $item['code'] ) ) { 00364 continue; 00365 } 00366 00367 if ( isset( $item['message'] ) && 00368 ( $message === false || !in_array( $message, $item['message'] ) ) 00369 ) { 00370 continue; 00371 } 00372 00373 return true; 00374 } 00375 00376 return false; 00377 } 00378 00385 protected function checkLanguage( $code ) { 00386 # Syntax check only 00387 $results = array(); 00388 if ( $this->level === 0 ) { 00389 $this->L->getMessages( $code ); 00390 00391 return $results; 00392 } 00393 00394 $checkFunctions = $this->getChecks(); 00395 foreach ( $this->checks as $check ) { 00396 if ( $this->isCheckBlacklisted( $check, $code, false ) ) { 00397 $results[$check] = array(); 00398 continue; 00399 } 00400 00401 $callback = array( $this->L, $checkFunctions[$check] ); 00402 if ( !is_callable( $callback ) ) { 00403 throw new MWException( "Unkown check $check." ); 00404 } 00405 $results[$check] = call_user_func( $callback, $code ); 00406 } 00407 00408 return $results; 00409 } 00410 00417 protected function formatKey( $key, $code ) { 00418 if ( $this->doLinks ) { 00419 $displayKey = ucfirst( $key ); 00420 if ( $code == $this->wikiCode ) { 00421 return "[[{$this->linksPrefix}MediaWiki:$displayKey|$key]]"; 00422 } else { 00423 return "[[{$this->linksPrefix}MediaWiki:$displayKey/$code|$key]]"; 00424 } 00425 } else { 00426 return $key; 00427 } 00428 } 00429 00433 protected function outputText() { 00434 foreach ( $this->results as $code => $results ) { 00435 $translated = $this->L->getMessages( $code ); 00436 $translated = count( $translated['translated'] ); 00437 foreach ( $results as $check => $messages ) { 00438 $count = count( $messages ); 00439 if ( $count ) { 00440 if ( $check == 'untranslated' ) { 00441 $translatable = $this->L->getGeneralMessages(); 00442 $total = count( $translatable['translatable'] ); 00443 } elseif ( in_array( $check, $this->nonMessageChecks() ) ) { 00444 $totalCount = $this->getTotalCount(); 00445 $totalCount = $totalCount[$check]; 00446 $callback = array( $this->L, $totalCount[0] ); 00447 $callCode = $totalCount[1] ? $totalCount[1] : $code; 00448 $total = count( call_user_func( $callback, $callCode ) ); 00449 } else { 00450 $total = $translated; 00451 } 00452 $search = array( '$1', '$2', '$3' ); 00453 $replace = array( $count, $total, $code ); 00454 $descriptions = $this->getDescriptions(); 00455 echo "\n" . str_replace( $search, $replace, $descriptions[$check] ) . "\n"; 00456 if ( $this->level == 1 ) { 00457 echo "[messages are hidden]\n"; 00458 } else { 00459 foreach ( $messages as $key => $value ) { 00460 if ( !in_array( $check, $this->nonMessageChecks() ) ) { 00461 $key = $this->formatKey( $key, $code ); 00462 } 00463 if ( $this->level == 2 || empty( $value ) ) { 00464 echo "* $key\n"; 00465 } else { 00466 echo "* $key: '$value'\n"; 00467 } 00468 } 00469 } 00470 } 00471 } 00472 } 00473 } 00474 00478 function outputWiki() { 00479 $detailText = ''; 00480 $rows[] = '! Language !! Code !! Total !! ' . 00481 implode( ' !! ', array_diff( $this->checks, $this->nonMessageChecks() ) ); 00482 foreach ( $this->results as $code => $results ) { 00483 $detailTextForLang = "==$code==\n"; 00484 $numbers = array(); 00485 $problems = 0; 00486 $detailTextForLangChecks = array(); 00487 foreach ( $results as $check => $messages ) { 00488 if ( in_array( $check, $this->nonMessageChecks() ) ) { 00489 continue; 00490 } 00491 $count = count( $messages ); 00492 if ( $count ) { 00493 $problems += $count; 00494 $messageDetails = array(); 00495 foreach ( $messages as $key => $details ) { 00496 $displayKey = $this->formatKey( $key, $code ); 00497 $messageDetails[] = $displayKey; 00498 } 00499 $detailTextForLangChecks[] = "=== $code-$check ===\n* " . implode( ', ', $messageDetails ); 00500 $numbers[] = "'''[[#$code-$check|$count]]'''"; 00501 } else { 00502 $numbers[] = $count; 00503 } 00504 } 00505 00506 if ( count( $detailTextForLangChecks ) ) { 00507 $detailText .= $detailTextForLang . implode( "\n", $detailTextForLangChecks ) . "\n"; 00508 } 00509 00510 if ( !$problems ) { 00511 # Don't list languages without problems 00512 continue; 00513 } 00514 $language = Language::fetchLanguageName( $code ); 00515 $rows[] = "| $language || $code || $problems || " . implode( ' || ', $numbers ); 00516 } 00517 00518 $tableRows = implode( "\n|-\n", $rows ); 00519 00520 $version = SpecialVersion::getVersion( 'nodb' ); 00521 // @codingStandardsIgnoreStart Long line. 00522 echo <<<EOL 00523 '''Check results are for:''' <code>$version</code> 00524 00525 00526 {| class="sortable wikitable" border="2" cellpadding="4" cellspacing="0" style="background-color: #F9F9F9; border: 1px #AAAAAA solid; border-collapse: collapse; clear: both;" 00527 $tableRows 00528 |} 00529 00530 $detailText 00531 00532 EOL; 00533 // @codingStandardsIgnoreEnd 00534 } 00535 00540 protected function isEmpty() { 00541 foreach ( $this->results as $results ) { 00542 foreach ( $results as $messages ) { 00543 if ( !empty( $messages ) ) { 00544 return false; 00545 } 00546 } 00547 } 00548 00549 return true; 00550 } 00551 } 00552 00556 class CheckExtensionsCLI extends CheckLanguageCLI { 00557 private $extensions; 00558 00564 public function __construct( array $options, $extension ) { 00565 if ( isset( $options['help'] ) ) { 00566 echo $this->help(); 00567 exit( 1 ); 00568 } 00569 00570 if ( isset( $options['lang'] ) ) { 00571 $this->code = $options['lang']; 00572 } else { 00573 global $wgLanguageCode; 00574 $this->code = $wgLanguageCode; 00575 } 00576 00577 if ( isset( $options['level'] ) ) { 00578 $this->level = $options['level']; 00579 } 00580 00581 $this->doLinks = isset( $options['links'] ); 00582 00583 if ( isset( $options['wikilang'] ) ) { 00584 $this->wikiCode = $options['wikilang']; 00585 } 00586 00587 if ( isset( $options['whitelist'] ) ) { 00588 $this->checks = explode( ',', $options['whitelist'] ); 00589 } elseif ( isset( $options['blacklist'] ) ) { 00590 $this->checks = array_diff( 00591 isset( $options['easy'] ) ? $this->easyChecks() : $this->defaultChecks(), 00592 explode( ',', $options['blacklist'] ) 00593 ); 00594 } elseif ( isset( $options['easy'] ) ) { 00595 $this->checks = $this->easyChecks(); 00596 } else { 00597 $this->checks = $this->defaultChecks(); 00598 } 00599 00600 if ( isset( $options['output'] ) ) { 00601 $this->output = $options['output']; 00602 } 00603 00604 # Some additional checks not enabled by default 00605 if ( isset( $options['duplicate'] ) ) { 00606 $this->checks[] = 'duplicate'; 00607 } 00608 00609 $this->extensions = array(); 00610 $extensions = new PremadeMediawikiExtensionGroups(); 00611 $extensions->addAll(); 00612 if ( $extension == 'all' ) { 00613 foreach ( MessageGroups::singleton()->getGroups() as $group ) { 00614 if ( strpos( $group->getId(), 'ext-' ) === 0 && !$group->isMeta() ) { 00615 $this->extensions[] = new ExtensionLanguages( $group ); 00616 } 00617 } 00618 } elseif ( $extension == 'wikimedia' ) { 00619 $wikimedia = MessageGroups::getGroup( 'ext-0-wikimedia' ); 00620 foreach ( $wikimedia->wmfextensions() as $extension ) { 00621 $group = MessageGroups::getGroup( $extension ); 00622 $this->extensions[] = new ExtensionLanguages( $group ); 00623 } 00624 } elseif ( $extension == 'flaggedrevs' ) { 00625 foreach ( MessageGroups::singleton()->getGroups() as $group ) { 00626 if ( strpos( $group->getId(), 'ext-flaggedrevs-' ) === 0 && !$group->isMeta() ) { 00627 $this->extensions[] = new ExtensionLanguages( $group ); 00628 } 00629 } 00630 } else { 00631 $extensions = explode( ',', $extension ); 00632 foreach ( $extensions as $extension ) { 00633 $group = MessageGroups::getGroup( 'ext-' . $extension ); 00634 if ( $group ) { 00635 $extension = new ExtensionLanguages( $group ); 00636 $this->extensions[] = $extension; 00637 } else { 00638 print "No such extension $extension.\n"; 00639 } 00640 } 00641 } 00642 } 00643 00648 protected function defaultChecks() { 00649 return array( 00650 'untranslated', 'duplicate', 'obsolete', 'variables', 'empty', 'plural', 00651 'whitespace', 'xhtml', 'chars', 'links', 'unbalanced', 00652 ); 00653 } 00654 00659 protected function nonMessageChecks() { 00660 return array(); 00661 } 00662 00667 protected function easyChecks() { 00668 return array( 00669 'duplicate', 'obsolete', 'empty', 'whitespace', 'xhtml', 'chars', 00670 ); 00671 } 00672 00677 protected function help() { 00678 return <<<ENDS 00679 Run this script to check the status of a specific language in extensions, or 00680 all of them. Command line settings are in form --parameter[=value], except for 00681 the first one. 00682 Parameters: 00683 * First parameter (mandatory): Extension name, multiple extension names 00684 (separated by commas), "all" for all the extensions, "wikimedia" for 00685 extensions used by Wikimedia or "flaggedrevs" for all FLaggedRevs 00686 extension messages. 00687 * lang: Language code (default: the installation default language). 00688 * help: Show this help. 00689 * level: Show the following display level (default: 2). 00690 * links: Link the message values (default off). 00691 * wikilang: For the links, what is the content language of the wiki to 00692 display the output in (default en). 00693 * whitelist: Do only the following checks (form: code,code). 00694 * blacklist: Do not perform the following checks (form: code,code). 00695 * easy: Do only the easy checks, which can be treated by non-speakers of 00696 the language. 00697 00698 Check codes (ideally, all of them should result 0; all the checks are executed 00699 by default (except language-specific check blacklists in checkLanguage.inc): 00700 * untranslated: Messages which are required to translate, but are not 00701 translated. 00702 * duplicate: Messages which translation equal to fallback. 00703 * obsolete: Messages which are untranslatable, but translated. 00704 * variables: Messages without variables which should be used, or with 00705 variables which should not be used. 00706 * empty: Empty messages. 00707 * whitespace: Messages which have trailing whitespace. 00708 * xhtml: Messages which are not well-formed XHTML (checks only few common 00709 errors). 00710 * chars: Messages with hidden characters. 00711 * links: Messages which contains broken links to pages (does not find all). 00712 * unbalanced: Messages which contains unequal numbers of opening {[ and 00713 closing ]}. 00714 00715 Display levels (default: 2): 00716 * 0: Skip the checks (useful for checking syntax). 00717 * 1: Show only the stub headers and number of wrong messages, without list 00718 of messages. 00719 * 2: Show only the headers and the message keys, without the message 00720 values. 00721 * 3: Show both the headers and the complete messages, with both keys and 00722 values. 00723 00724 ENDS; 00725 } 00726 00730 public function execute() { 00731 $this->doChecks(); 00732 } 00733 00739 protected function checkLanguage( $code ) { 00740 foreach ( $this->extensions as $extension ) { 00741 $this->L = $extension; 00742 $this->results = array(); 00743 $this->results[$code] = parent::checkLanguage( $code ); 00744 00745 if ( !$this->isEmpty() ) { 00746 echo $extension->name() . ":\n"; 00747 00748 if ( $this->level > 0 ) { 00749 switch ( $this->output ) { 00750 case 'plain': 00751 $this->outputText(); 00752 break; 00753 case 'wiki': 00754 $this->outputWiki(); 00755 break; 00756 default: 00757 throw new MWException( "Invalid output type $this->output" ); 00758 } 00759 } 00760 00761 echo "\n"; 00762 } 00763 } 00764 } 00765 } 00766 00767 // Blacklist some checks for some languages or some messages 00768 // Possible keys of the sub arrays are: 'check', 'code' and 'message'. 00769 $checkBlacklist = array( 00770 array( 00771 'check' => 'plural', 00772 'code' => array( 'az', 'bo', 'cdo', 'dz', 'id', 'fa', 'gan', 'gan-hans', 00773 'gan-hant', 'gn', 'hak', 'hu', 'ja', 'jv', 'ka', 'kk-arab', 00774 'kk-cyrl', 'kk-latn', 'km', 'kn', 'ko', 'lzh', 'mn', 'ms', 00775 'my', 'sah', 'sq', 'tet', 'th', 'to', 'tr', 'vi', 'wuu', 'xmf', 00776 'yo', 'yue', 'zh', 'zh-classical', 'zh-cn', 'zh-hans', 00777 'zh-hant', 'zh-hk', 'zh-sg', 'zh-tw', 'zh-yue' 00778 ), 00779 ), 00780 array( 00781 'check' => 'chars', 00782 'code' => array( 'my' ), 00783 ), 00784 );