MediaWiki  REL1_23
checkLanguage.inc
Go to the documentation of this file.
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 );