Index: trunk/extensions/Translate/SpecialImportTranslations.php |
— | — | @@ -48,14 +48,38 @@ |
49 | 49 | return; |
50 | 50 | } |
51 | 51 | |
52 | | - // Proceed to loading and parsing if possible |
53 | | - $file = null; |
54 | | - $msg = $this->loadFile( $file ); |
55 | | - if ( $this->checkError( $msg ) ) return; |
| 52 | + if ( $this->request->getCheck( 'process' ) ) { |
| 53 | + $data = $this->getCachedData(); |
| 54 | + if (!$data) { |
| 55 | + $this->out->addWikiMsg( 'session_fail_preview' ); // Core... bad |
| 56 | + $this->outputForm(); |
| 57 | + return; |
| 58 | + } |
56 | 59 | |
57 | | - $msg = $this->parseFile( $file ); |
58 | | - if ( $this->checkError( $msg ) ) return; |
| 60 | + } else { |
| 61 | + // Proceed to loading and parsing if possible |
| 62 | + // TODO: use a Status object instead? |
| 63 | + $file = null; |
| 64 | + $msg = $this->loadFile( $file ); |
| 65 | + if ( $this->checkError( $msg ) ) return; |
59 | 66 | |
| 67 | + $msg = $this->parseFile( $file ); |
| 68 | + if ( $this->checkError( $msg ) ) return; |
| 69 | + |
| 70 | + $data = $msg[1]; |
| 71 | + $this->setCachedData( $data ); |
| 72 | + } |
| 73 | + |
| 74 | + $messages = $data['MESSAGES']; |
| 75 | + $group = $data['METADATA']['group']; |
| 76 | + $code = $data['METADATA']['code']; |
| 77 | + |
| 78 | + $importer = new MessageWebImporter( $this->getTitle(), $group, $code ); |
| 79 | + $alldone = $importer->execute( $messages ); |
| 80 | + if ( $alldone ) { |
| 81 | + $this->deleteCachedData(); |
| 82 | + } |
| 83 | + |
60 | 84 | } |
61 | 85 | |
62 | 86 | /** |
— | — | @@ -80,8 +104,8 @@ |
81 | 105 | protected function outputForm() { |
82 | 106 | $this->out->addScriptClass( 'TranslateImport' ); |
83 | 107 | |
| 108 | + // Ugly but necessary form building ahead, ohoy |
84 | 109 | $this->out->addHTML( |
85 | | - |
86 | 110 | Xml::openElement( 'form', array( |
87 | 111 | 'action' => $this->getTitle()->getLocalUrl(), |
88 | 112 | 'method' => 'post', |
— | — | @@ -143,7 +167,6 @@ |
144 | 168 | |
145 | 169 | $url = $this->request->getText( 'upload-url' ); |
146 | 170 | $status = Http::doDownload( $url, false ); |
147 | | - var_dump( $status ); |
148 | 171 | if ( $status->isOk() ) { |
149 | 172 | $filedata = $status->value; |
150 | 173 | return array( 'ok' ); |
— | — | @@ -170,19 +193,62 @@ |
171 | 194 | } |
172 | 195 | } |
173 | 196 | |
| 197 | + /** |
| 198 | + * Try parsing file. |
| 199 | + */ |
174 | 200 | protected function parseFile( $data ) { |
175 | | - $matches = array(); |
176 | | - if ( preg_match( '/X-Language-Code:\s+([a-zA-Z-_]+)/', $data, $matches ) ) { |
177 | | - $code = $matches[1]; |
178 | | - } else { |
179 | | - return array( 'no-language-code' ); |
| 201 | + // Construct a dummy group for us... |
| 202 | + // Time to rethink the interface again? |
| 203 | + $group = MessageGroupBase::factory( |
| 204 | + array( |
| 205 | + 'FILES' => array( |
| 206 | + 'class' => 'GettextFFS', |
| 207 | + 'CtxtAsKey' => true, |
| 208 | + ), |
| 209 | + 'BASIC' => array( |
| 210 | + 'class' => 'FileBasedMessageGroup', |
| 211 | + 'namespace' => -1, |
| 212 | + ) |
| 213 | + ) |
| 214 | + ); |
| 215 | + |
| 216 | + $ffs = new GettextFFS( $group ); |
| 217 | + $data = $ffs->readFromVariable( $data ); |
| 218 | + |
| 219 | + // Special data added by GettextFFS |
| 220 | + $metadata = $data['METADATA']; |
| 221 | + |
| 222 | + // This should catch everything that is not a po file exported form us |
| 223 | + if ( !isset($metadata['code']) || !isset($metadata['group']) ) { |
| 224 | + return array( 'no-headers' ); |
180 | 225 | } |
181 | 226 | |
182 | | - if ( preg_match( '/X-Message-Group:\s+([a-zA-Z0-9-._]+)/', $data, $matches ) ) { |
183 | | - $groupId = $matches[1]; |
184 | | - } else { |
185 | | - return array( 'no-group-id' ); |
| 227 | + // And check for stupid editors which like to drop msgctxt.. |
| 228 | + // which unfortunately breaks submission |
| 229 | + if ( isset($metadata['warnings']) ) { |
| 230 | + global $wgLang; |
| 231 | + return array( 'warnings', $wgLang->commaList( $metadata['warnings'] ) ); |
186 | 232 | } |
| 233 | + |
| 234 | + return array( 'ok', $data ); |
187 | 235 | } |
188 | 236 | |
| 237 | + protected function setCachedData( $data ) { |
| 238 | + global $wgMemc; |
| 239 | + $key = wfMemcKey( 'translate', 'webimport', $this->user->getId() ); |
| 240 | + $wgMemc->set( $key, $data, 60*15 ); // 15 minutes |
| 241 | + } |
| 242 | + |
| 243 | + protected function getCachedData() { |
| 244 | + global $wgMemc; |
| 245 | + $key = wfMemcKey( 'translate', 'webimport', $this->user->getId() ); |
| 246 | + return $wgMemc->get( $key ); |
| 247 | + } |
| 248 | + |
| 249 | + protected function deleteCachedData() { |
| 250 | + global $wgMemc; |
| 251 | + $key = wfMemcKey( 'translate', 'webimport', $this->user->getId() ); |
| 252 | + return $wgMemc->delete( $key ); |
| 253 | + } |
| 254 | + |
189 | 255 | } |
\ No newline at end of file |
Index: trunk/extensions/Translate/_autoload.php |
— | — | @@ -81,6 +81,7 @@ |
82 | 82 | $wgAutoloadClasses['JsSelectToInput'] = $dir . 'utils/JsSelectToInput.php'; |
83 | 83 | $wgAutoloadClasses['HTMLJsSelectToInputField'] = $dir . 'utils/HTMLJsSelectToInputField.php'; |
84 | 84 | $wgAutoloadClasses['MessageGroupCache'] = $dir . 'utils/MessageGroupCache.php'; |
| 85 | +$wgAutoloadClasses['MessageWebImporter'] = $dir . 'utils/MessageWebImporter.php'; |
85 | 86 | |
86 | 87 | |
87 | 88 | # predefined groups |
Index: trunk/extensions/Translate/utils/MessageWebImporter.php |
— | — | @@ -0,0 +1,364 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * Class which encapsulates message importing. It scans for changes (new, changed, deleted), |
| 5 | + * displays them in pretty way with diffs and finally executes the actions the user choices. |
| 6 | + * |
| 7 | + * @addtogroup Extensions |
| 8 | + * |
| 9 | + * @author Niklas Laxström |
| 10 | + * @copyright Copyright © 2009, Niklas Laxström |
| 11 | + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later |
| 12 | + */ |
| 13 | + |
| 14 | +class MessageWebImporter { |
| 15 | + protected $title; |
| 16 | + protected $user; |
| 17 | + protected $group; |
| 18 | + protected $code; |
| 19 | + |
| 20 | + protected $processingTime = 10; // Seconds |
| 21 | + |
| 22 | + public function __construct( Title $title = null, $group = null, $code = 'en' ) { |
| 23 | + $this->setTitle( $title ); |
| 24 | + $this->setGroup( $group ); |
| 25 | + $this->setCode( $code ); |
| 26 | + } |
| 27 | + |
| 28 | + |
| 29 | + // Wrapper for consistency with SpecialPage |
| 30 | + public function getTitle() { return $this->title; } |
| 31 | + public function setTitle( Title $title ) { $this->title = $title; } |
| 32 | + |
| 33 | + |
| 34 | + public function getUser() { |
| 35 | + global $wgUser; |
| 36 | + return $this->user ? $this->user : $wgUser; |
| 37 | + } |
| 38 | + public function setUser( User $user ) { $this->user = $user; } |
| 39 | + |
| 40 | + |
| 41 | + public function getGroup() { return $this->group; } |
| 42 | + /** |
| 43 | + * Group is either MessageGroup object or group id. |
| 44 | + */ |
| 45 | + public function setGroup( $group ) { |
| 46 | + if ( $group instanceof MessageGroup ) { |
| 47 | + $this->group = $group; |
| 48 | + } else { |
| 49 | + $this->group = MessageGroups::getGroup( $group ); |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + public function getCode() { return $this->code; } |
| 54 | + public function setCode( $code = 'en' ) { $this->code = $code; } |
| 55 | + |
| 56 | + |
| 57 | + protected function getAction() { |
| 58 | + return $this->getTitle()->getFullURL(); |
| 59 | + } |
| 60 | + |
| 61 | + protected function doHeader() { |
| 62 | + TranslateUtils::injectCSS(); |
| 63 | + |
| 64 | + $formParams = array( |
| 65 | + 'method' => 'post', |
| 66 | + 'action' => $this->getAction(), |
| 67 | + 'class' => 'mw-translate-manage' |
| 68 | + ); |
| 69 | + |
| 70 | + return |
| 71 | + Xml::openElement( 'form', $formParams ) . |
| 72 | + Xml::hidden( 'title', $this->getTitle()->getPrefixedText() ) . |
| 73 | + Xml::hidden( 'token', $this->getUser()->editToken() ) . |
| 74 | + Xml::hidden( 'process', 1 ) |
| 75 | + ; |
| 76 | + } |
| 77 | + |
| 78 | + protected function doFooter() { |
| 79 | + return '</form>'; |
| 80 | + } |
| 81 | + |
| 82 | + protected function allowProcess() { |
| 83 | + global $wgRequest; |
| 84 | + if ( $wgRequest->wasPosted() && |
| 85 | + $wgRequest->getBool( 'process', false ) && |
| 86 | + $this->getUser()->matchEditToken( $wgRequest->getVal( 'token' ) ) ) { |
| 87 | + |
| 88 | + return true; |
| 89 | + } |
| 90 | + return false; |
| 91 | + } |
| 92 | + |
| 93 | + protected function getActions() { |
| 94 | + if ( $this->code === 'en' ) { |
| 95 | + return array( 'import', 'fuzzy', 'ignore' ); |
| 96 | + } else { |
| 97 | + return array( 'import', 'conflict', 'ignore' ); |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + protected function getDefaultAction( $fuzzy, $action ) { |
| 102 | + if ( $action ) return $action; |
| 103 | + return $fuzzy ? 'conflict' : 'import'; |
| 104 | + } |
| 105 | + |
| 106 | + |
| 107 | + public function execute( $messages ) { |
| 108 | + global $wgOut; |
| 109 | + $this->out = $wgOut; |
| 110 | + |
| 111 | + // Set up diff engine |
| 112 | + $diff = new DifferenceEngine; |
| 113 | + $diff->showDiffStyle(); |
| 114 | + $diff->setReducedLineNumbers(); |
| 115 | + |
| 116 | + // Check whether we do processing |
| 117 | + $process = $this->allowProcess(); |
| 118 | + |
| 119 | + // Initialise collection |
| 120 | + $group = $this->getGroup(); |
| 121 | + $code = $this->getCode(); |
| 122 | + $collection = $group->initCollection( $code ); |
| 123 | + $collection->loadTranslations(); |
| 124 | + |
| 125 | + $this->out->addHTML( $this->doHeader() ); |
| 126 | + |
| 127 | + // Determine changes |
| 128 | + $alldone = $process; |
| 129 | + $changed = array(); |
| 130 | + foreach ( $messages as $key => $value ) { |
| 131 | + $fuzzy = $old = false; |
| 132 | + if ( isset($collection[$key]) ) { |
| 133 | + $old = $collection[$key]->translation(); |
| 134 | + $fuzzy = TranslateEditAddons::hasFuzzyString( $old ) || |
| 135 | + TranslateEditAddons::isFuzzy( self::makeTitle( $group, $key, $code ) ); |
| 136 | + } |
| 137 | + |
| 138 | + // No changes at all, ignore |
| 139 | + if ( strval($old) === strval($value) ) continue; |
| 140 | + |
| 141 | + if ( $old === false ) { |
| 142 | + $name = wfMsgHtml( 'translate-manage-import-new', |
| 143 | + '<code style="font-weight:normal;">' . htmlspecialchars($key) . '</code>' |
| 144 | + ); |
| 145 | + $text = TranslateUtils::convertWhiteSpaceToHTML( $value ); |
| 146 | + $changed[] = $this->makeSectionElement( $name, 'new', $text ); |
| 147 | + } else { |
| 148 | + $diff->setText( $old, $value ); |
| 149 | + $text = $diff->getDiff( '', '' ); |
| 150 | + $type = 'changed'; |
| 151 | + |
| 152 | + global $wgRequest; |
| 153 | + $action = $wgRequest->getVal( "action-$type-$key" ); |
| 154 | + |
| 155 | + if ( $process ) { |
| 156 | + if ( !count($changed) ) $changed[] = '<ul>'; |
| 157 | + |
| 158 | + global $wgLang; |
| 159 | + if ( $action === null ) { |
| 160 | + $message = wfMsgExt( 'translate-manage-inconsistent', 'parseinline', wfEscapeWikiText( "action-$type-$key" ) ); |
| 161 | + $changed[] = "<li>$message</li></ul>"; |
| 162 | + $process = false; |
| 163 | + } else { |
| 164 | + // Check processing time |
| 165 | + if ( !isset($this->time) ) $this->time = wfTimestamp(); |
| 166 | + |
| 167 | + $message = $this->doAction( $action, $group, $key, $code, $value ); |
| 168 | + |
| 169 | + $key = array_shift( $message ); |
| 170 | + $params = $message; |
| 171 | + $message = wfMsgExt( $key, 'parseinline', $params ); |
| 172 | + $changed[] = "<li>$message</li>"; |
| 173 | + |
| 174 | + if ( $this->checkProcessTime() ) { |
| 175 | + $process = false; |
| 176 | + $duration = $wgLang->formatNum( $this->processingTime ); |
| 177 | + $message = wfMsgExt( 'translate-manage-toolong', 'parseinline', $duration ); |
| 178 | + $changed[] = "<li>$message</li></ul>"; |
| 179 | + } |
| 180 | + continue; |
| 181 | + } |
| 182 | + } |
| 183 | + |
| 184 | + $alldone = false; |
| 185 | + |
| 186 | + $actions = $this->getActions(); |
| 187 | + $defaction = $this->getDefaultAction( $fuzzy, $action ); |
| 188 | + |
| 189 | + $act = array(); |
| 190 | + |
| 191 | + foreach ( $actions as $action ) { |
| 192 | + $label = wfMsg( "translate-manage-action-$action" ); |
| 193 | + $act[] = Xml::radioLabel( $label, "action-$type-$key", $action, "action-$key-$action", $action === $defaction ); |
| 194 | + } |
| 195 | + |
| 196 | + $name = wfMsg( 'translate-manage-import-diff', |
| 197 | + '<code style="font-weight:normal;">' . htmlspecialchars($key) . '</code>', |
| 198 | + implode( ' ', $act ) |
| 199 | + ); |
| 200 | + |
| 201 | + $changed[] = $this->makeSectionElement( $name, $type, $text ); |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | + |
| 206 | + if ( !$process ) { |
| 207 | + $collection->filter( 'hastranslation', false ); |
| 208 | + $keys = array_keys($collection->keys()); |
| 209 | + |
| 210 | + $diff = array_diff( $keys, array_keys($messages) ); |
| 211 | + |
| 212 | + foreach ( $diff as $s ) { |
| 213 | + $name = wfMsgHtml( 'translate-manage-import-deleted', |
| 214 | + '<code style="font-weight:normal;">' . htmlspecialchars($s) . '</code>' |
| 215 | + ); |
| 216 | + $text = TranslateUtils::convertWhiteSpaceToHTML( $collection[$s]->translation() ); |
| 217 | + $changed[] = $this->makeSectionElement( $name, 'deleted', $text ); |
| 218 | + } |
| 219 | + } |
| 220 | + |
| 221 | + if ( $process || (!count($changed) && $code !== 'en') ) { |
| 222 | + if ( !count($changed) ) $this->out->addWikiMsg( 'translate-manage-nochanges-other' ); |
| 223 | + |
| 224 | + if ( !count($changed) || strpos( $changed[count($changed)-1], '<li>' ) !== 0 ) $changed[] = '<ul>'; |
| 225 | + |
| 226 | + $message = wfMsgExt( 'translate-manage-import-done', 'parseinline' ); |
| 227 | + $changed[] = "<li>$message</li></ul>"; |
| 228 | + $this->out->addHTML( implode( "\n", $changed ) ); |
| 229 | + } else { |
| 230 | + |
| 231 | + // END |
| 232 | + |
| 233 | + if ( count($changed) ) { |
| 234 | + if ( $code === 'en' ) { |
| 235 | + $this->out->addWikiMsg( 'translate-manage-intro-en' ); |
| 236 | + } else { |
| 237 | + global $wgLang; |
| 238 | + $lang = TranslateUtils::getLanguageName( $code, false, $wgLang->getCode() ); |
| 239 | + $this->out->addWikiMsg( 'translate-manage-intro-other', $lang ); |
| 240 | + } |
| 241 | + $this->out->addHTML( Xml::hidden( 'language', $code ) ); |
| 242 | + $this->out->addHTML( implode( "\n", $changed ) ); |
| 243 | + $this->out->addHTML( Xml::submitButton( wfMsg( 'translate-manage-submit' ) ) ); |
| 244 | + } else { |
| 245 | + $this->out->addWikiMsg( 'translate-manage-nochanges' ); |
| 246 | + } |
| 247 | + } |
| 248 | + |
| 249 | + $this->out->addHTML( $this->doFooter() ); |
| 250 | + return $alldone; |
| 251 | + } |
| 252 | + |
| 253 | + protected function doAction( $action, $group, $key, $code, $message, $comment = '' ) { |
| 254 | + if ( $action === 'import' || $action === 'conflict' ) { |
| 255 | + |
| 256 | + if ( $action === 'import' ) { |
| 257 | + $comment = wfMsgForContentNoTrans( 'translate-manage-import-summary' ); |
| 258 | + } else { |
| 259 | + $comment = wfMsgForContentNoTrans( 'translate-manage-conflict-summary' ); |
| 260 | + $message = TRANSLATE_FUZZY . $message; |
| 261 | + } |
| 262 | + |
| 263 | + $title = self::makeTitle( $group, $key, $code ); |
| 264 | + return $this->doImport( $title, $message, $comment ); |
| 265 | + } elseif ( $action === 'ignore' ) { |
| 266 | + return array( 'translate-manage-import-ignore', $key ); |
| 267 | + } elseif ( $action === 'fuzzy' ) { |
| 268 | + return $this->doFuzzy( $title, $message, $comment ); |
| 269 | + } else { |
| 270 | + throw new MWException( "Unhandled action $action" ); |
| 271 | + } |
| 272 | + } |
| 273 | + |
| 274 | + protected function checkProcessTime() { |
| 275 | + return wfTimestamp() - $this->time >= $this->processingTime; |
| 276 | + } |
| 277 | + |
| 278 | + protected function doImport( $title, $message, $comment, $user = null ) { |
| 279 | + $flags = EDIT_FORCE_BOT; |
| 280 | + $article = new Article( $title ); |
| 281 | + $status = $article->doEdit( $message, $comment, $flags ); |
| 282 | + $success = $status->isOK(); |
| 283 | + |
| 284 | + if ( $success ) { |
| 285 | + return array( 'translate-manage-import-ok', |
| 286 | + wfEscapeWikiText( $title->getPrefixedText() ) |
| 287 | + ); |
| 288 | + } else { |
| 289 | + throw new MWException( "Failed to import new version of page {$title->getPrefixedText()}\n{$status->getWikiText()}" ); |
| 290 | + } |
| 291 | + } |
| 292 | + |
| 293 | + protected function doFuzzy( $title, $message, $comment ) { |
| 294 | + $dbw = wfGetDB( DB_MASTER ); |
| 295 | + $titleText = $title->getDBKey(); |
| 296 | + $condArray = array( |
| 297 | + 'page_namespace' => $title->getNamespace(), |
| 298 | + 'page_latest=rev_id', |
| 299 | + 'rev_text_id=old_id', |
| 300 | + "page_title LIKE '{$dbw->escapeLike( $titleText )}/%%'" |
| 301 | + ); |
| 302 | + |
| 303 | + $rows = $dbr->select( |
| 304 | + array( 'page', 'revision', 'text' ), |
| 305 | + array( 'page_title', 'page_namespace', 'old_text', 'old_flags' ), |
| 306 | + $conds, |
| 307 | + __METHOD__ |
| 308 | + ); |
| 309 | + |
| 310 | + $changed = array(); |
| 311 | + $fuzzybot = self::getFuzzyBot(); |
| 312 | + foreach ( $rows as $row ) { |
| 313 | + $ttitle = Title::makeTitle( $row->page_namespace, $row->page_title ); |
| 314 | + |
| 315 | + $changed[] = $this->doImport( |
| 316 | + $ttitle, |
| 317 | + TRANSLATE_FUZZY . Revision::getRevisionText( $row ), |
| 318 | + $comment, |
| 319 | + $fuzzybot |
| 320 | + ); |
| 321 | + |
| 322 | + if ( $this->checkProcessTime() ) break; |
| 323 | + |
| 324 | + } |
| 325 | + |
| 326 | + if ( count($changed) === count($rows) ) { |
| 327 | + $comment = wfMsgForContentNoTrans( 'translate-manage-import-summary' ); |
| 328 | + $changed[] = $this->doImport( $title, $message, $comment ); |
| 329 | + } |
| 330 | + |
| 331 | + $text = ''; |
| 332 | + foreach ( $changed as $c ) { |
| 333 | + $key = array_shift( $c ); |
| 334 | + $text = "* " . wfMsgExt( $key, array(), $c ); |
| 335 | + } |
| 336 | + |
| 337 | + return array( 'translate-manage-import-fuzzy', |
| 338 | + "\n" . $text |
| 339 | + ); |
| 340 | + } |
| 341 | + |
| 342 | + protected static function getFuzzyBot() { |
| 343 | + global $wgTranslateFuzzyBotName; |
| 344 | + $user = User::newFromName( $wgTranslateFuzzyBotName ); |
| 345 | + if ( !$user->isLoggedIn() ) $user->addToDatabase(); |
| 346 | + return $user; |
| 347 | + } |
| 348 | + |
| 349 | + protected static function makeTitle( $group, $key, $code ) { |
| 350 | + $ns = $group->getNamespace(); |
| 351 | + $titlekey = "$key/$code"; |
| 352 | + return Title::makeTitleSafe( $ns, $titlekey ); |
| 353 | + } |
| 354 | + |
| 355 | + protected function makeSectionElement( $legend, $type, $content ) { |
| 356 | + $containerParams = array( 'class' => "mw-tpt-sp-section mw-tpt-sp-section-type-{$type}" ); |
| 357 | + $legendParams = array( 'class' => 'mw-translate-manage-legend' ); |
| 358 | + $contentParams = array( 'class' => 'mw-tpt-sp-content' ); |
| 359 | + |
| 360 | + return Xml::tags( 'div', $containerParams, |
| 361 | + Xml::tags( 'div', $legendParams, $legend ) . |
| 362 | + Xml::tags( 'div', $contentParams, $content ) |
| 363 | + ); |
| 364 | + } |
| 365 | +} |
\ No newline at end of file |
Property changes on: trunk/extensions/Translate/utils/MessageWebImporter.php |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 366 | + native |
Index: trunk/extensions/Translate/Translate.i18n.php |
— | — | @@ -295,10 +295,11 @@ |
296 | 296 | 'translate-import-err-invalid-title' => 'Provided file name <nowiki>$1</nowiki> was invalid.', |
297 | 297 | 'translate-import-err-no-such-file' => 'File <nowiki>$1</nowiki> does not exist or has not been uploaded locally.', |
298 | 298 | |
299 | | - 'translate-import-err-no-language-code' => 'File is not a well formed Gettext file in Translate extension format: |
300 | | -Unable to determine language code.', |
301 | | - 'translate-import-err-no-language-code' => 'File is not a well formed Gettext file in Translate extension format: |
302 | | -Unable to determine group ID.', |
| 299 | + 'translate-import-err-no-headers' => 'File is not a well formed Gettext file in Translate extension format: |
| 300 | +Unable to determine group and language from file headers.', |
| 301 | + 'translate-import-err-warnings' => 'The file is not well formed. |
| 302 | +Make sure your editor does not remove msgctxt fields. |
| 303 | +Details: $1', |
303 | 304 | ); |
304 | 305 | |
305 | 306 | /** Message documentation (Message documentation) |