20 public const UNIT_MARKER_INVALID_CHARS =
"_/\n<>";
21 public const NEW_UNIT_ID =
'-1';
23 public const TVAR_OLD_SYNTAX_REGEX =
'~<tvar\|([^>]+)>(.*?)</>~us';
25 public const TVAR_NEW_SYNTAX_REGEX =
28 <tvar \s+ name \s* = \s*
29 ( (
' (?<key1> [^']* )
' ) | ( " (?<key2> [^"]* ) " ) | (?<key3> [^"'\s>]* ) )
30 \s* > (?<value>.*?) </tvar \s* >
41 public $oldText =
null;
46 protected $inline =
false;
48 private $canWrap =
true;
52 private static $properties = [
'version',
'id',
'text',
'type',
'oldText',
'inline' ];
54 public function __construct(
56 string $id = self::NEW_UNIT_ID,
58 string $oldText =
null
63 $this->oldText = $oldText;
66 public function setIsInline(
bool $value ):
void {
67 $this->
inline = $value;
70 public function isInline():
bool {
74 public function setCanWrap(
bool $value ):
void {
75 $this->canWrap = $value;
78 public function canWrap():
bool {
79 return $this->canWrap;
83 public function getText():
string {
88 public function getTextWithVariables():
string {
89 $variableReplacements = [];
90 foreach ( $this->getVariables() as $variable ) {
91 $variableReplacements[$variable->getDefinition()] = $variable->getName();
94 return strtr( $this->text, $variableReplacements );
98 public function getTextForTrans():
string {
99 $variableReplacements = [];
100 foreach ( $this->getVariables() as $variable ) {
101 $variableReplacements[$variable->getDefinition()] = $variable->getValue();
104 return strtr( $this->text, $variableReplacements );
108 public function getMarkedText():
string {
110 $header =
"<!--T:$id-->";
112 if ( $this->getHeading( $this->text ) !==
null ) {
113 $text = $this->text .
' ' . $header;
115 if ( $this->
inline ) {
116 $text = $header .
' ' . $this->text;
118 $text = $header .
"\n" . $this->text;
126 public function getOldText():
string {
127 return $this->oldText ?? $this->text;
131 public function getVariables(): array {
135 preg_match_all( self::TVAR_OLD_SYNTAX_REGEX, $this->text, $matches, PREG_SET_ORDER );
136 foreach ( $matches as $m ) {
141 preg_match_all( self::TVAR_NEW_SYNTAX_REGEX, $this->text, $matches, PREG_SET_ORDER );
142 foreach ( $matches as $m ) {
146 '$' . ( $m[
'key1'] . $m[
'key2'] . $m[
'key3'] ),
155 public function serializeToArray(): array {
157 foreach ( self::$properties as $index => $property ) {
160 $data[$index] = $this->$property;
166 public static function unserializeFromArray( array $data ):
self {
168 $unit =
new self(
'' );
169 foreach ( self::$properties as $index => $property ) {
170 $unit->$property = $data[$index];
176 public function getTextForRendering(
178 Language $sourceLanguage,
179 Language $targetLanguage,
180 bool $wrapUntranslated,
181 ?Parser $parser =
null
188 $headingText = $this->getHeading( $msg->
definition() );
190 if ( $msg->
hasTag(
'fuzzy' ) ) {
192 $content = str_replace( TRANSLATE_FUZZY,
'', $content );
193 $attributes[
'class'] =
'mw-translate-fuzzy';
195 $translationLanguage = $targetLanguage->getCode();
197 $content = $this->getTextWithVariables();
198 if ( $wrapUntranslated ) {
199 $attributes[
'lang'] = $sourceLanguage->getHtmlCode();
200 $attributes[
'dir'] = $sourceLanguage->getDir();
201 $attributes[
'class'] =
'mw-content-' . $sourceLanguage->getDir();
203 $translationLanguage = $sourceLanguage->getCode();
206 if ( $this->canWrap() && $attributes ) {
207 $tag = $this->isInline() ?
'span' :
'div';
208 $content = $this->isInline() ? $content :
"\n$content\n";
209 $content = Html::rawElement( $tag, $attributes, $content );
212 $variableReplacements = [];
213 foreach ( $this->getVariables() as $variable ) {
214 $variableReplacements[$variable->getName()] = $variable->getValue();
219 $this->shouldAddAnchor(
227 $sectionName = substr( $parser->guessSectionNameFromWikiText( $headingText ), 1 );
228 $attributes = [
'id' => $sectionName ];
229 $content = Html::rawElement(
'span', $attributes,
'' ) .
"\n$content";
232 $content = strtr( $content, $variableReplacements );
235 $content = preg_replace(
236 '/{{\s*TRANSLATIONLANGUAGE\s*}}/',
237 $translationLanguage,
245 public function getIssues(): array {
246 $issues = $usedNames = [];
247 foreach ( $this->getVariables() as $variable ) {
248 $name = $variable->getName();
250 if ( !preg_match( $pattern, $name ) ) {
253 TranslationUnitIssue::WARNING,
254 'tpt-validation-not-insertable',
255 [ wfEscapeWikiText( $name ) ]
259 $usedNames[ $name ][] = $variable->getValue();
262 foreach ( $usedNames as $name => $contents ) {
263 $uniqueValueCount = count( array_unique( $contents ) );
264 if ( $uniqueValueCount > 1 ) {
266 TranslationUnitIssue::ERROR,
267 'tpt-validation-name-reuse',
268 [ wfEscapeWikiText( $name ) ]
273 return array_values( $issues );
277 private function getHeading(
string $text ): ?
string {
279 preg_match(
'/^(={1,6})[ \t]*(.+?)[ \t]*\1\s*$/', $text, $match );
280 return $match[2] ??
null;
283 private function shouldAddAnchor(
284 Language $sourceLanguage,
285 Language $targetLanguage,
286 ?
string $headingText,
291 if ( $headingText ===
null ) {
296 if ( $sourceLanguage->getCode() === $targetLanguage->getCode() ) {
307 if ( !$this->canWrap() ) {