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