Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageBundleStore.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\MessageBundleTranslation;
5
6use InvalidArgumentException;
7use MediaWiki\Context\RequestContext;
15use MediaWiki\JobQueue\JobQueueGroup;
16use MediaWiki\Language\LanguageNameUtils;
17use MediaWiki\Message\Message;
18use MediaWiki\Revision\RevisionRecord;
19use MediaWiki\Specials\SpecialPageLanguage;
20use MediaWiki\Title\Title;
21
29 private const METADATA_KEYS_DB = [
30 'priorityforce',
31 'prioritylangs'
32 ];
33
34 public function __construct(
35 private readonly RevTagStore $revTagStore,
36 private readonly JobQueueGroup $jobQueue,
37 private readonly LanguageNameUtils $languageNameUtils,
38 private readonly MessageIndex $messageIndex,
39 private readonly MessageGroupMetadata $messageGroupMetadata,
40 ) {
41 }
42
43 public function move( Title $oldName, Title $newName ): void {
44 $oldBundle = new MessageBundle( $oldName );
45 $newBundle = new MessageBundle( $newName );
46
47 $this->messageGroupMetadata->moveMetadata(
48 $oldBundle->getMessageGroupId(),
49 $newBundle->getMessageGroupId(),
50 self::METADATA_KEYS_DB
51 );
52
53 MessageBundle::clearSourcePageCache();
54
55 MessageGroups::singleton()->recache();
56 // Update message index now so that, when after this job the MoveTranslationUnits hook
57 // runs in deferred updates, it will not run RebuildMessageIndexJob (T175834).
58 // Notice: currently this code is only called on CLI or in jobs, but this is not very
59 // obvious. messageIndex->rebuild() should never be called during web requests due to
60 // its slowness.
61 $this->messageIndex->rebuild();
62 }
63
64 public function handleNullRevisionInsert( TranslatableBundle $bundle, RevisionRecord $revision ): void {
65 if ( !$bundle instanceof MessageBundle ) {
66 throw new InvalidArgumentException(
67 'Expected $bundle to be of type MessageBundle, got ' . get_class( $bundle )
68 );
69 }
70
71 $this->revTagStore->replaceTag( $bundle->getTitle(), RevTagStore::MB_VALID_TAG, $revision->getId() );
72 MessageBundle::clearSourcePageCache();
73 }
74
75 public function delete( Title $title ): void {
76 $this->revTagStore->removeTags( $title, RevTagStore::MB_VALID_TAG );
77
78 $bundle = new MessageBundle( $title );
79 $this->messageGroupMetadata->clearMetadata( $bundle->getMessageGroupId(), self::METADATA_KEYS_DB );
80
81 MessageBundle::clearSourcePageCache();
82
83 MessageGroups::singleton()->recache();
84 $this->jobQueue->push( RebuildMessageIndexJob::newJob( __METHOD__ ) );
85 }
86
88 public function validate( Title $pageTitle, MessageBundleContent $content ): void {
89 $content->validate();
90 // Verify that the language code is valid
91 $metadata = $content->getMetadata();
92 $sourceLanguageCode = $metadata->getSourceLanguageCode();
93 if ( $sourceLanguageCode ) {
94 if ( !$this->languageNameUtils->isKnownLanguageTag( $sourceLanguageCode ) ) {
95 throw new MalformedBundle(
96 'translate-messagebundle-error-invalid-sourcelanguage', [ $sourceLanguageCode ]
97 );
98 }
99
100 $revisionId = $this->revTagStore->getLatestRevisionWithTag( $pageTitle, RevTagStore::MB_VALID_TAG );
101 // If request wants the source language to be changed after creation, then throw an exception
102 if ( $revisionId !== null && $sourceLanguageCode !== $pageTitle->getPageLanguage()->getCode() ) {
103 throw new MalformedBundle( 'translate-messagebundle-sourcelanguage-changed' );
104 }
105
106 }
107
108 $priorityLanguageCodes = $metadata->getPriorityLanguages();
109 if ( $priorityLanguageCodes ) {
110 $invalidLanguageCodes = [];
111 foreach ( $priorityLanguageCodes as $languageCode ) {
112 if ( !is_string( $languageCode ) ) {
113 throw new MalformedBundle( 'translate-messagebundle-error-invalid-prioritylanguage-format' );
114 }
115
116 if ( !$this->languageNameUtils->isKnownLanguageTag( $languageCode ) ) {
117 $invalidLanguageCodes[] = $languageCode;
118 }
119 }
120
121 if ( $invalidLanguageCodes ) {
122 throw new MalformedBundle(
123 'translate-messagebundle-error-invalid-prioritylanguage',
124 [ Message::listParam( $invalidLanguageCodes ), count( $invalidLanguageCodes ) ]
125 );
126 }
127 }
128 }
129
130 public function save(
131 Title $pageTitle,
132 RevisionRecord $revisionRecord,
133 MessageBundleContent $content
134 ): void {
135 // Validate the content before saving
136 $this->validate( $pageTitle, $content );
137
138 $previousRevisionId = $this->revTagStore->getLatestRevisionWithTag( $pageTitle, RevTagStore::MB_VALID_TAG );
139 if ( $previousRevisionId !== null ) {
140 $this->revTagStore->removeTags( $pageTitle, RevTagStore::MB_VALID_TAG );
141 }
142
143 if ( $content->isValid() ) {
144 // Bundle is valid and contains translatable messages
145 $this->revTagStore->replaceTag( $pageTitle, RevTagStore::MB_VALID_TAG, $revisionRecord->getId() );
146 MessageBundle::clearSourcePageCache();
147
148 // Defer most of the heavy work to the job queue
149 $job = UpdateMessageBundleJob::newJob( $pageTitle, $revisionRecord->getId(), $previousRevisionId );
150
151 $this->jobQueue->push( $job );
152
153 $metadata = $content->getMetadata();
154
155 if ( $previousRevisionId === null ) {
156 // A new message bundle, set the source language.
157 $definedLanguageCode = $metadata->getSourceLanguageCode();
158 $pageLanguageCode = $pageTitle->getPageLanguage()->getCode();
159 if ( $definedLanguageCode !== null && $definedLanguageCode !== $pageLanguageCode ) {
160 $context = RequestContext::getMain();
161 SpecialPageLanguage::changePageLanguage(
162 $context,
163 $pageTitle,
164 $definedLanguageCode,
165 $context->msg( 'translate-messagebundle-change-sourcelanguage' )->inContentLanguage()->text()
166 );
167 }
168 }
169
170 // Save the metadata
171 $messageBundle = new MessageBundle( $pageTitle );
172 $groupId = $messageBundle->getMessageGroupId();
173
174 $priorityForce = $metadata->areOnlyPriorityLanguagesAllowed() ? 'on' : false;
175 $priorityLanguages = $metadata->getPriorityLanguages();
176 $priorityLanguages = $priorityLanguages ? implode( ',', $priorityLanguages ) : false;
177
178 $this->messageGroupMetadata->set( $groupId, 'prioritylangs', $priorityLanguages );
179 $this->messageGroupMetadata->set( $groupId, 'priorityforce', $priorityForce );
180
181 $description = $metadata->getDescription();
182 $this->messageGroupMetadata->set( $groupId, 'description', $description ?? false );
183
184 $label = $metadata->getLabel();
185 $this->messageGroupMetadata->set( $groupId, 'label', $label ?? false );
186 }
187
188 // What should we do if there are no messages? Use the previous version? Remove the group?
189 // Currently, the bundle is removed from translation.
190 }
191}
Factory class for accessing message groups individually by id or all of them as a list.
Class to manage revision tags for translatable bundles.
Translatable bundle represents a message group where its translatable content is defined on a wiki pa...
getTitle()
Return the title of the page where the translatable bundle is defined.
getMessageGroupId()
Return the message group id for the bundle Note that the message group id may refer to a message grou...
Creates a database of keys in all groups, so that namespace and key can be used to get the groups the...
Offers functionality for reading and updating Translate group related metadata.