Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
poimport.php
Go to the documentation of this file.
1<?php
14use MediaWiki\MediaWikiServices;
15use MediaWiki\Title\Title;
16
17// Standard boilerplate to define $IP
18if ( getenv( 'MW_INSTALL_PATH' ) !== false ) {
19 $IP = getenv( 'MW_INSTALL_PATH' );
20} else {
21 $dir = __DIR__;
22 $IP = "$dir/../../..";
23}
24require_once "$IP/maintenance/Maintenance.php";
25
26class Poimport extends Maintenance {
27 public function __construct() {
28 parent::__construct();
29 $this->addDescription( 'Po file importer (does not make changes unless specified).' );
30 $this->addOption(
31 'file',
32 'Gettext file to import (Translate specific formatting)',
33 true, /*required*/
34 true /*has arg*/
35 );
36 $this->addOption(
37 'user',
38 'User who makes edits to wiki',
39 true, /*required*/
40 true /*has arg*/
41 );
42 $this->addOption(
43 'really',
44 '(optional) Actually make changes',
45 false, /*required*/
46 false /*has arg*/
47 );
48 $this->requireExtension( 'Translate' );
49 }
50
51 public function execute() {
52 // Parse the po file.
53 $p = new PoImporter( $this->getOption( 'file' ) );
54 $p->setProgressCallback( [ $this, 'myOutput' ] );
55 [ $changes, $group ] = $p->parse();
56
57 if ( !count( $changes ) ) {
58 $this->output( "No changes to import\n" );
59 exit( 0 );
60 }
61
62 // Import changes to wiki.
63 $w = new WikiWriter(
64 $changes,
65 $group,
66 $this->getOption( 'user' ),
67 !$this->hasOption( 'really' )
68 );
69
70 $w->setProgressCallback( [ $this, 'myOutput' ] );
71 $w->execute();
72 }
73
81 public function myOutput( $text, $channel = null, $error = false ) {
82 if ( $error ) {
83 $this->error( $text );
84 } else {
85 $this->output( $text, $channel );
86 }
87 }
88}
89
96 private $progressCallback;
101 private $file;
102
104 public function __construct( $file ) {
105 $this->file = $file;
106 }
107
108 public function setProgressCallback( $callback ) {
109 $this->progressCallback = $callback;
110 }
111
113 protected function reportProgress( $text, $channel = null, $severity = 'status' ) {
114 if ( is_callable( $this->progressCallback ) ) {
115 $useErrorOutput = $severity === 'error';
116 call_user_func( $this->progressCallback, $text, $channel, $useErrorOutput );
117 }
118 }
119
127 protected function initMessages( $id, $code ) {
128 $group = MessageGroups::getGroup( $id );
129
130 $messages = $group->initCollection( $code );
131 $messages->loadTranslations();
132
133 return $messages;
134 }
135
140 public function parse() {
141 $data = file_get_contents( $this->file );
142 $data = TextContent::normalizeLineEndings( $data );
143
144 $matches = [];
145 if ( preg_match( '/X-Language-Code:\s+(.*)\\\n/', $data, $matches ) ) {
146 $code = $matches[1];
147 $this->reportProgress( "Detected language as $code", 'code' );
148 } else {
149 $this->reportProgress( 'Unable to determine language code', 'code', 'error' );
150
151 return false;
152 }
153
154 if ( preg_match( '/X-Message-Group:\s+(.*)\\\n/', $data, $matches ) ) {
155 $groupId = $matches[1];
156 $this->reportProgress( "Detected message group as $groupId", 'group' );
157 } else {
158 $this->reportProgress( 'Unable to determine message group', 'group', 'error' );
159
160 return false;
161 }
162
163 $contents = $this->initMessages( $groupId, $code );
164
165 echo "----\n";
166
167 $poformat = '".*"\n?(^".*"$\n?)*';
168 $quotePattern = '/(^"|"$\n?)/m';
169
170 $sections = preg_split( '/\n{2,}/', $data );
171 $changes = [];
172 foreach ( $sections as $section ) {
173 $matches = [];
174 if ( preg_match( "/^msgctxt\s($poformat)/mx", $section, $matches ) ) {
175 // Remove quoting
176 $key = preg_replace( $quotePattern, '', $matches[1] );
177
178 // Ignore unknown keys
179 if ( !isset( $contents[$key] ) ) {
180 continue;
181 }
182 } else {
183 continue;
184 }
185 $matches = [];
186 if ( preg_match( "/^msgstr\s($poformat)/mx", $section, $matches ) ) {
187 // Remove quoting
188 $translation = preg_replace( $quotePattern, '', $matches[1] );
189 // Restore new lines and remove quoting
190 $translation = stripcslashes( $translation );
191 } else {
192 continue;
193 }
194
195 // Fuzzy messages
196 if ( preg_match( '/^#, fuzzy$/m', $section ) ) {
197 $translation = TRANSLATE_FUZZY . $translation;
198 }
199
200 $oldtranslation = (string)$contents[$key]->translation();
201
202 if ( $translation !== $oldtranslation ) {
203 if ( $translation === '' ) {
204 $this->reportProgress( "Skipping empty translation in the po file for $key!\n" );
205 } else {
206 if ( $oldtranslation === '' ) {
207 $this->reportProgress( "New translation for $key\n" );
208 } else {
209 $this->reportProgress( "Translation of $key differs:\n$translation\n" );
210 }
211 $changes["$key/$code"] = $translation;
212 }
213 }
214 }
215
216 return [ $changes, $groupId ];
217 }
218}
219
225 private $progressCallback;
227 private $user;
229 private $changes;
231 private $dryrun;
233 private $group;
234
241 public function __construct( array $changes, $groupId, $user, $dryrun = true ) {
242 $this->changes = $changes;
243 $this->group = MessageGroups::getGroup( $groupId );
244 $this->user = User::newFromName( $user );
245 $this->dryrun = $dryrun;
246 }
247
248 public function setProgressCallback( $callback ) {
249 $this->progressCallback = $callback;
250 }
251
253 protected function reportProgress( $text, $channel, $severity = 'status' ) {
254 if ( is_callable( $this->progressCallback ) ) {
255 $useErrorOutput = $severity === 'error';
256 call_user_func( $this->progressCallback, $text, $channel, $useErrorOutput );
257 }
258 }
259
263 public function execute() {
264 if ( !$this->group ) {
265 $this->reportProgress( 'Given group does not exist.', 'groupId', 'error' );
266
267 return;
268 }
269
270 if ( !$this->user->idForName() ) {
271 $this->reportProgress( 'Given user does not exist.', 'user', 'error' );
272
273 return;
274 }
275
276 $count = count( $this->changes );
277 $this->reportProgress( "Going to update $count pages.", 'pagecount' );
278
279 $ns = $this->group->getNamespace();
280
281 foreach ( $this->changes as $title => $text ) {
282 $this->updateMessage( $ns, $title, $text );
283 }
284 }
285
292 private function updateMessage( $namespace, $page, $text ) {
293 $title = Title::makeTitleSafe( $namespace, $page );
294
295 if ( !$title instanceof Title ) {
296 $this->reportProgress( 'INVALID TITLE!', $page, 'error' );
297
298 return;
299 }
300 $this->reportProgress( "Updating {$title->getPrefixedText()}... ", $title );
301
302 if ( $this->dryrun ) {
303 $this->reportProgress( 'DRY RUN!', $title );
304
305 return;
306 }
307
308 $page = MediaWikiServices::getInstance()->getWikiPageFactory()->newFromTitle( $title );
309 $content = ContentHandler::makeContent( $text, $title );
310 $status = $page->doUserEditContent(
311 $content,
312 $this->user,
313 'Updating translation from gettext import'
314 );
315
316 if ( $status->isOK() ) {
317 $this->reportProgress( 'OK!', $title );
318 } else {
319 $this->reportProgress( 'Failed!', $title );
320 }
321 }
322}
323
324$maintClass = Poimport::class;
325require_once RUN_MAINTENANCE_IF_MAIN;
Factory class for accessing message groups individually by id or all of them as a list.
This file contains the class for core message collections implementation.
Parses a po file that has been exported from MediaWiki.
Definition poimport.php:94
parse()
Parses relevant stuff from the po file.
Definition poimport.php:140
reportProgress( $text, $channel=null, $severity='status')
Definition poimport.php:113
__construct( $file)
Definition poimport.php:104
initMessages( $id, $code)
Loads translations for comparison.
Definition poimport.php:127
myOutput( $text, $channel=null, $error=false)
Public alternative for protected Maintenance::output() as we need to get messages from the ChangeSync...
Definition poimport.php:81
Import changes to wiki as given user.
Definition poimport.php:223
execute()
Updates pages on by one.
Definition poimport.php:263
__construct(array $changes, $groupId, $user, $dryrun=true)
Definition poimport.php:241
reportProgress( $text, $channel, $severity='status')
Definition poimport.php:253