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