Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
ImportExternalTranslationsMaintenanceScript.php
1<?php
2declare( strict_types = 1 );
3
5
6use Exception;
14use MediaWiki\MediaWikiServices;
16use MessageGroup;
17use SpecialPage;
18
21 public function __construct() {
22 parent::__construct();
23 $this->addDescription( 'Script for processing message changes in file based message groups' );
24 $this->addOption(
25 'group',
26 '(optional) Comma separated list of group IDs to process (can use * as wildcard). ' .
27 'Default: "*"',
28 self::OPTIONAL,
29 self::HAS_ARG
30 );
31 $this->addOption(
32 'skipgroup',
33 '(optional) Comma separated list of group IDs to not process (can use * ' .
34 'as wildcard). Overrides --group parameter.',
35 self::OPTIONAL,
36 self::HAS_ARG
37 );
38 $this->addOption(
39 'name',
40 '(optional) Unique name to avoid conflicts with multiple invocations of this script.',
41 self::OPTIONAL,
42 self::HAS_ARG
43 );
44 $this->addOption(
45 'safe-import',
46 '(optional) Import "safe" changes: message additions when no other kind of changes.'
47 );
48 $this->addOption(
49 'skip-group-sync-check',
50 '(optional) Skip importing group if synchronization is still in progress or if there ' .
51 'was an error during synchronization. See: ' .
52 'https://www.mediawiki.org/wiki/Help:Extension:Translate/Group_management#Strong_synchronization'
53 );
54 $this->addOption(
55 'import-non-renames',
56 '(optional) Import non renames: if a language in a group has only additions and changes to existing ' .
57 ' strings, then the additions are imported'
58 );
59 $this->requireExtension( 'Translate' );
60 }
61
62 public function execute() {
63 $name = $this->getOption( 'name', MessageChangeStorage::DEFAULT_NAME );
64 if ( !MessageChangeStorage::isValidCdbName( $name ) ) {
65 $this->fatalError( 'Invalid name' );
66 }
67
68 $groups = $this->getGroups();
69 $changes = [];
71
72 $importStrategy = $this->getImportStrategy();
73 $skipGroupSyncCache = $this->hasOption( 'skip-group-sync-check' );
74
75 $services = Services::getInstance();
76 $groupSyncCache = $services->getGroupSynchronizationCache();
77 $groupSyncCacheEnabled = MediaWikiServices::getInstance()->getMainConfig()
78 ->get( 'TranslateGroupSynchronizationCache' );
79
80 foreach ( $groups as $id => $group ) {
81 if ( !$group instanceof FileBasedMessageGroup ) {
82 $this->error(
83 "Group $id expected to be FileBasedMessageGroup, got " . get_class( $group ) . " instead."
84 );
85 continue;
86 }
87
88 if ( $groupSyncCacheEnabled && !$skipGroupSyncCache ) {
89 if ( $groupSyncCache->isGroupBeingProcessed( $id ) ) {
90 $this->error( "Group $id is currently being synchronized; skipping processing of changes\n" );
91 continue;
92 }
93
94 if ( $groupSyncCache->groupHasErrors( $id ) ) {
95 $this->error( "Skipping $id due to an error during synchronization\n" );
96 continue;
97 }
98 }
99
100 if ( $importStrategy === ExternalMessageSourceStateImporter::IMPORT_NONE ) {
101 $this->output( "Processing $id\n" );
102 }
103
104 try {
105 $changes[$id] = $comparator->processGroup( $group, $comparator::ALL_LANGUAGES );
106 } catch ( Exception $e ) {
107 $errorMsg = "Exception occurred while processing group: $id.\nException: $e";
108 $this->error( $errorMsg );
109 error_log( $errorMsg );
110 }
111 }
112
113 // Remove all groups without changes
114 $changes = array_filter( $changes, static function ( MessageSourceChange $change ) {
115 return $change->getAllModifications() !== [];
116 } );
117
118 if ( $changes === [] ) {
119 if ( $importStrategy === ExternalMessageSourceStateImporter::IMPORT_NONE ) {
120 $this->output( "No changes found\n" );
121 }
122
123 return;
124 }
125
126 if ( $importStrategy !== ExternalMessageSourceStateImporter::IMPORT_NONE ) {
127 $importer = $services->getExternalMessageSourceStateImporter();
128 $info = $importer->import( $changes, $name, $importStrategy );
129 $this->printChangeInfo( $info );
130
131 return;
132 }
133
134 $file = MessageChangeStorage::getCdbPath( $name );
135
136 MessageChangeStorage::writeChanges( $changes, $file );
137 $url = SpecialPage::getTitleFor( 'ManageMessageGroups', $name )->getFullURL();
138 $this->output( "Process changes at $url\n" );
139 }
140
145 private function getGroups(): array {
146 $groups = MessageGroups::getGroupsByType( FileBasedMessageGroup::class );
147
148 // Include all if option not given
149 $include = $this->getOption( 'group', '*' );
150 $include = explode( ',', $include );
151 $include = array_map( 'trim', $include );
152 $include = MessageGroups::expandWildcards( $include );
153
154 // Exclude nothing if option not given
155 $exclude = $this->getOption( 'skipgroup', '' );
156 $exclude = explode( ',', $exclude );
157 $exclude = array_map( 'trim', $exclude );
158 $exclude = MessageGroups::expandWildcards( $exclude );
159
160 // Flip to allow isset
161 $include = array_flip( $include );
162 $exclude = array_flip( $exclude );
163
164 $groups = array_filter( $groups,
165 static function ( MessageGroup $group ) use ( $include, $exclude ) {
166 $id = $group->getId();
167
168 return isset( $include[$id] ) && !isset( $exclude[$id] );
169 }
170 );
171
172 return $groups;
173 }
174
175 private function printChangeInfo( array $info ): void {
176 foreach ( $info['processed'] as $group => $languages ) {
177 $newMessageCount = array_sum( $languages );
178 if ( $newMessageCount ) {
179 $this->output( "Imported $newMessageCount new messages or translations for $group.\n" );
180 }
181 }
182
183 if ( $info['skipped'] !== [] ) {
184 $skipped = implode( ', ', array_keys( $info['skipped'] ) );
185 $this->output( "There are changes to check for groups $skipped.\n" );
186 $url = SpecialPage::getTitleFor( 'ManageMessageGroups', $info['name'] )->getFullURL();
187 $this->output( "You can process them at $url\n" );
188 }
189 }
190
191 private function getImportStrategy(): int {
192 $importStrategy = ExternalMessageSourceStateImporter::IMPORT_NONE;
193 if ( $this->hasOption( 'safe-import' ) ) {
194 $importStrategy = ExternalMessageSourceStateImporter::IMPORT_SAFE;
195 }
196
197 if ( $this->hasOption( 'import-non-renames' ) ) {
198 $importStrategy = ExternalMessageSourceStateImporter::IMPORT_NON_RENAMES;
199 }
200
201 return $importStrategy;
202 }
203}
This class implements default behavior for file based message groups.
Factory class for accessing message groups individually by id or all of them as a list.
Class is used to track the changes made when importing messages from the remote sources using importE...
Minimal service container.
Definition Services.php:44
Constants for making code for maintenance scripts more readable.
A simple string comparator, that compares two strings and determines if they are an exact match.
Interface for message groups.
getId()
Returns the unique identifier for this group.
Finds external changes for file based message groups.