Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
TranslationUnit.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\PageTranslation;
5
6use Html;
7use Language;
8use TMessage;
9use const PREG_SET_ORDER;
10
19 public const UNIT_MARKER_INVALID_CHARS = "_/\n<>";
20 public const NEW_UNIT_ID = '-1';
21 // Deprecated syntax. Example: <tvar|1>...</>
22 public const TVAR_OLD_SYNTAX_REGEX = '~<tvar\|([^>]+)>(.*?)</>~us';
23 // Current syntax. Example: <tvar name=1>...</tvar>
24 public const TVAR_NEW_SYNTAX_REGEX = <<<'REGEXP'
25~
26<tvar \s+ name \s* = \s*
27( ( ' (?<key1> [^']* ) ' ) | ( " (?<key2> [^"]* ) " ) | (?<key3> [^"'\s>]* ) )
28\s* > (?<value>.*?) </tvar \s* >
29~xusi
30REGEXP;
31
33 public $id;
35 public $text;
37 public $type;
39 public $oldText = null;
44 protected $inline = false;
46 private $canWrap = true;
48 private $version = 1;
50 private static $properties = [ 'version', 'id', 'text', 'type', 'oldText', 'inline' ];
51
52 public function __construct(
53 string $text,
54 string $id = self::NEW_UNIT_ID,
55 string $type = 'new',
56 string $oldText = null
57 ) {
58 $this->text = $text;
59 $this->id = $id;
60 $this->type = $type;
61 $this->oldText = $oldText;
62 }
63
64 public function setIsInline( bool $value ): void {
65 $this->inline = $value;
66 }
67
68 public function isInline(): bool {
69 return $this->inline;
70 }
71
72 public function setCanWrap( bool $value ): void {
73 $this->canWrap = $value;
74 }
75
76 public function canWrap(): bool {
77 return $this->canWrap;
78 }
79
81 public function getText(): string {
82 return $this->text;
83 }
84
86 public function getTextWithVariables(): string {
87 $variableReplacements = [];
88 foreach ( $this->getVariables() as $variable ) {
89 $variableReplacements[$variable->getDefinition()] = $variable->getName();
90 }
91
92 return strtr( $this->text, $variableReplacements );
93 }
94
96 public function getTextForTrans(): string {
97 $variableReplacements = [];
98 foreach ( $this->getVariables() as $variable ) {
99 $variableReplacements[$variable->getDefinition()] = $variable->getValue();
100 }
101
102 return strtr( $this->text, $variableReplacements );
103 }
104
106 public function getMarkedText(): string {
107 $id = $this->id;
108 $header = "<!--T:$id-->";
109 $re = '~^(=+.*?=+\s*?$)~m';
110 $rep = "\\1 $header";
111 $count = 0;
112
113 $text = preg_replace( $re, $rep, $this->text, 1, $count );
114
115 if ( $count === 0 ) {
116 if ( $this->inline ) {
117 $text = $header . ' ' . $this->text;
118 } else {
119 $text = $header . "\n" . $this->text;
120 }
121 }
122
123 return $text;
124 }
125
127 public function getOldText(): string {
128 return $this->oldText ?? $this->text;
129 }
130
132 public function getVariables(): array {
133 $vars = [];
134
135 $matches = [];
136 preg_match_all( self::TVAR_OLD_SYNTAX_REGEX, $this->text, $matches, PREG_SET_ORDER );
137 foreach ( $matches as $m ) {
138 $vars[] = new TranslationVariable( $m[0], '$' . $m[1], $m[2] );
139 }
140
141 $matches = [];
142 preg_match_all( self::TVAR_NEW_SYNTAX_REGEX, $this->text, $matches, PREG_SET_ORDER );
143 foreach ( $matches as $m ) {
144 $vars[] = new TranslationVariable(
145 $m[0],
146 // Maximum of one of these is non-empty string
147 '$' . ( $m['key1'] . $m['key2'] . $m['key3'] ),
148 $m['value']
149 );
150 }
151
152 return $vars;
153 }
154
156 public function serializeToArray(): array {
157 $data = [];
158 foreach ( self::$properties as $index => $property ) {
159 // Because this is used for the JobQueue, use a list
160 // instead of an array to save space.
161 $data[$index] = $this->$property;
162 }
163
164 return $data;
165 }
166
167 public static function unserializeFromArray( array $data ): self {
168 // Give dummy default text, will be overridden
169 $unit = new self( '' );
170 foreach ( self::$properties as $index => $property ) {
171 $unit->$property = $data[$index];
172 }
173
174 return $unit;
175 }
176
177 public function getTextForRendering(
178 ?TMessage $msg,
179 Language $sourceLanguage,
180 Language $targetLanguage,
181 bool $wrapUntranslated
182 ): string {
183 $attributes = [];
184
185 if ( $msg && $msg->translation() !== null ) {
186 $content = $msg->translation();
187 if ( $msg->hasTag( 'fuzzy' ) ) {
188 // We do not ever want to show explicit fuzzy marks in the rendered pages
189 $content = str_replace( TRANSLATE_FUZZY, '', $content );
190 $attributes['class'] = 'mw-translate-fuzzy';
191 }
192 $translationLanguage = $targetLanguage->getCode();
193 } else {
194 $content = $this->getTextWithVariables();
195 if ( $wrapUntranslated ) {
196 $attributes['lang'] = $sourceLanguage->getHtmlCode();
197 $attributes['dir'] = $sourceLanguage->getDir();
198 $attributes['class'] = 'mw-content-' . $sourceLanguage->getDir();
199 }
200 $translationLanguage = $sourceLanguage->getCode();
201 }
202
203 if ( $this->canWrap() && $attributes ) {
204 $tag = $this->isInline() ? 'span' : 'div';
205 $content = $this->isInline() ? $content : "\n$content\n";
206 $content = Html::rawElement( $tag, $attributes, $content );
207 }
208
209 $variableReplacements = [];
210 foreach ( $this->getVariables() as $variable ) {
211 $variableReplacements[$variable->getName()] = $variable->getValue();
212 }
213
214 $content = strtr( $content, $variableReplacements );
215
216 // Allow wrapping this inside variables
217 $content = preg_replace(
218 '/{{\s*TRANSLATIONLANGUAGE\s*}}/',
219 $translationLanguage,
220 $content
221 );
222
223 return $content;
224 }
225
227 public function getIssues(): array {
228 $issues = $usedNames = [];
229 foreach ( $this->getVariables() as $variable ) {
230 $name = $variable->getName();
231 $pattern = '/^' . TranslatablePageInsertablesSuggester::NAME_PATTERN . '$/u';
232 if ( !preg_match( $pattern, $name ) ) {
233 // Key by name to avoid multiple issues of the same name
234 $issues[$name] = new TranslationUnitIssue(
235 TranslationUnitIssue::WARNING,
236 'tpt-validation-not-insertable',
237 [ wfEscapeWikiText( $name ) ]
238 );
239 }
240
241 $usedNames[ $name ][] = $variable->getValue();
242 }
243
244 foreach ( $usedNames as $name => $contents ) {
245 $uniqueValueCount = count( array_unique( $contents ) );
246 if ( $uniqueValueCount > 1 ) {
247 $issues[] = new TranslationUnitIssue(
248 TranslationUnitIssue::ERROR,
249 'tpt-validation-name-reuse',
250 [ wfEscapeWikiText( $name ) ]
251 );
252 }
253 }
254
255 return array_values( $issues );
256 }
257}
return[ 'Translate:ConfigHelper'=> static function():ConfigHelper { return new ConfigHelper();}, 'Translate:CsvTranslationImporter'=> static function(MediaWikiServices $services):CsvTranslationImporter { return new CsvTranslationImporter( $services->getWikiPageFactory());}, 'Translate:EntitySearch'=> static function(MediaWikiServices $services):EntitySearch { return new EntitySearch($services->getMainWANObjectCache(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), MessageGroups::singleton(), $services->getNamespaceInfo(), $services->get( 'Translate:MessageIndex'), $services->getTitleParser(), $services->getTitleFormatter());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->getMainConfig(), $services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance( 'Translate.GroupSynchronization'), MessageIndex::singleton());}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore(new RevTagStore(), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, 'Translate:MessageGroupReview'=> static function(MediaWikiServices $services):MessageGroupReview { return new MessageGroupReview($services->getDBLoadBalancer(), $services->getHookContainer());}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=$services->getMainConfig() ->get( 'TranslateMessageIndex');if(is_string( $params)) { $params=(array) $params;} $class=array_shift( $params);return new $class( $params);}, 'Translate:ParsingPlaceholderFactory'=> static function():ParsingPlaceholderFactory { return new ParsingPlaceholderFactory();}, 'Translate:PersistentCache'=> static function(MediaWikiServices $services):PersistentCache { return new PersistentDatabaseCache($services->getDBLoadBalancer(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'));}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleMover'=> static function(MediaWikiServices $services):TranslatableBundleMover { return new TranslatableBundleMover($services->getMovePageFactory(), $services->getJobQueueGroup(), $services->getLinkBatchFactory(), $services->get( 'Translate:TranslatableBundleFactory'), $services->get( 'Translate:SubpageListBuilder'), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), new RevTagStore(), $services->getDBLoadBalancer());}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnectionRef(DB_REPLICA);return new TranslationStashStorage( $db);}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory());}, 'Translate:TranslationUnitStoreFactory'=> static function(MediaWikiServices $services):TranslationUnitStoreFactory { return new TranslationUnitStoreFactory( $services->getDBLoadBalancer());}, 'Translate:TranslatorActivity'=> static function(MediaWikiServices $services):TranslatorActivity { $query=new TranslatorActivityQuery($services->getMainConfig(), $services->getDBLoadBalancer());return new TranslatorActivity($services->getMainObjectStash(), $query, $services->getJobQueueGroup());}, 'Translate:TtmServerFactory'=> static function(MediaWikiServices $services):TtmServerFactory { $config=$services->getMainConfig();$default=$config->get( 'TranslateTranslationDefaultService');if( $default===false) { $default=null;} return new TtmServerFactory( $config->get( 'TranslateTranslationServices'), $default);}]
@phpcs-require-sorted-array
This class represents one translation unit in a translatable page.
getTextForTrans()
Returns unit text with variables replaced.
getMarkedText()
Returns the unit text with updated or added unit marker.
getTextWithVariables()
Returns the text with tvars replaces with placeholders.
getOldText()
Returns oldtext, or current text if not available.
This class represents one translation variable in a translation unit.
Interface for message objects used by MessageCollection.
Definition Message.php:14
hasTag( $tag)
Check if this message has a given tag.
Definition Message.php:90
translation()
Get the message translation.