Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Tests\locale\Functional;
|
Chris@0
|
4
|
Chris@18
|
5 use Drupal\Core\Database\Database;
|
Chris@18
|
6 use Drupal\Core\File\FileSystemInterface;
|
Chris@0
|
7 use Drupal\Core\StreamWrapper\PublicStream;
|
Chris@0
|
8 use Drupal\file\Entity\File;
|
Chris@0
|
9 use Drupal\Tests\BrowserTestBase;
|
Chris@17
|
10 use Drupal\Component\Render\FormattableMarkup;
|
Chris@0
|
11
|
Chris@0
|
12 /**
|
Chris@0
|
13 * Base class for testing updates to string translations.
|
Chris@0
|
14 */
|
Chris@0
|
15 abstract class LocaleUpdateBase extends BrowserTestBase {
|
Chris@0
|
16
|
Chris@0
|
17 /**
|
Chris@0
|
18 * Timestamp for an old translation.
|
Chris@0
|
19 *
|
Chris@0
|
20 * @var int
|
Chris@0
|
21 */
|
Chris@0
|
22 protected $timestampOld;
|
Chris@0
|
23
|
Chris@0
|
24 /**
|
Chris@0
|
25 * Timestamp for a medium aged translation.
|
Chris@0
|
26 *
|
Chris@0
|
27 * @var int
|
Chris@0
|
28 */
|
Chris@0
|
29 protected $timestampMedium;
|
Chris@0
|
30
|
Chris@0
|
31 /**
|
Chris@0
|
32 * Timestamp for a new translation.
|
Chris@0
|
33 *
|
Chris@0
|
34 * @var int
|
Chris@0
|
35 */
|
Chris@0
|
36 protected $timestampNew;
|
Chris@0
|
37
|
Chris@0
|
38 /**
|
Chris@0
|
39 * Timestamp for current time.
|
Chris@0
|
40 *
|
Chris@0
|
41 * @var int
|
Chris@0
|
42 */
|
Chris@0
|
43 protected $timestampNow;
|
Chris@0
|
44
|
Chris@0
|
45 /**
|
Chris@0
|
46 * Modules to enable.
|
Chris@0
|
47 *
|
Chris@0
|
48 * @var array
|
Chris@0
|
49 */
|
Chris@0
|
50 public static $modules = ['locale', 'locale_test'];
|
Chris@0
|
51
|
Chris@0
|
52 /**
|
Chris@0
|
53 * {@inheritdoc}
|
Chris@0
|
54 */
|
Chris@0
|
55 protected function setUp() {
|
Chris@0
|
56 parent::setUp();
|
Chris@0
|
57
|
Chris@0
|
58 // Setup timestamps to identify old and new translation sources.
|
Chris@0
|
59 $this->timestampOld = REQUEST_TIME - 300;
|
Chris@0
|
60 $this->timestampMedium = REQUEST_TIME - 200;
|
Chris@0
|
61 $this->timestampNew = REQUEST_TIME - 100;
|
Chris@0
|
62 $this->timestampNow = REQUEST_TIME;
|
Chris@0
|
63
|
Chris@0
|
64 // Enable import of translations. By default this is disabled for automated
|
Chris@0
|
65 // tests.
|
Chris@0
|
66 $this->config('locale.settings')
|
Chris@0
|
67 ->set('translation.import_enabled', TRUE)
|
Chris@16
|
68 ->set('translation.use_source', LOCALE_TRANSLATION_USE_SOURCE_LOCAL)
|
Chris@0
|
69 ->save();
|
Chris@0
|
70 }
|
Chris@0
|
71
|
Chris@0
|
72 /**
|
Chris@0
|
73 * Sets the value of the default translations directory.
|
Chris@0
|
74 *
|
Chris@0
|
75 * @param string $path
|
Chris@0
|
76 * Path of the translations directory relative to the drupal installation
|
Chris@0
|
77 * directory.
|
Chris@0
|
78 */
|
Chris@0
|
79 protected function setTranslationsDirectory($path) {
|
Chris@18
|
80 \Drupal::service('file_system')->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY);
|
Chris@0
|
81 $this->config('locale.settings')->set('translation.path', $path)->save();
|
Chris@0
|
82 }
|
Chris@0
|
83
|
Chris@0
|
84 /**
|
Chris@0
|
85 * Adds a language.
|
Chris@0
|
86 *
|
Chris@0
|
87 * @param string $langcode
|
Chris@0
|
88 * The language code of the language to add.
|
Chris@0
|
89 */
|
Chris@0
|
90 protected function addLanguage($langcode) {
|
Chris@0
|
91 $edit = ['predefined_langcode' => $langcode];
|
Chris@0
|
92 $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
|
Chris@0
|
93 $this->container->get('language_manager')->reset();
|
Chris@17
|
94 $this->assertTrue(\Drupal::languageManager()->getLanguage($langcode), new FormattableMarkup('Language %langcode added.', ['%langcode' => $langcode]));
|
Chris@0
|
95 }
|
Chris@0
|
96
|
Chris@0
|
97 /**
|
Chris@0
|
98 * Creates a translation file and tests its timestamp.
|
Chris@0
|
99 *
|
Chris@0
|
100 * @param string $path
|
Chris@0
|
101 * Path of the file relative to the public file path.
|
Chris@0
|
102 * @param string $filename
|
Chris@0
|
103 * Name of the file to create.
|
Chris@0
|
104 * @param int $timestamp
|
Chris@0
|
105 * (optional) Timestamp to set the file to. Defaults to current time.
|
Chris@0
|
106 * @param array $translations
|
Chris@0
|
107 * (optional) Array of source/target value translation strings. Only
|
Chris@0
|
108 * singular strings are supported, no plurals. No double quotes are allowed
|
Chris@0
|
109 * in source and translations strings.
|
Chris@0
|
110 */
|
Chris@0
|
111 protected function makePoFile($path, $filename, $timestamp = NULL, array $translations = []) {
|
Chris@0
|
112 $timestamp = $timestamp ? $timestamp : REQUEST_TIME;
|
Chris@0
|
113 $path = 'public://' . $path;
|
Chris@0
|
114 $text = '';
|
Chris@0
|
115 $po_header = <<<EOF
|
Chris@0
|
116 msgid ""
|
Chris@0
|
117 msgstr ""
|
Chris@0
|
118 "Project-Id-Version: Drupal 8\\n"
|
Chris@0
|
119 "MIME-Version: 1.0\\n"
|
Chris@0
|
120 "Content-Type: text/plain; charset=UTF-8\\n"
|
Chris@0
|
121 "Content-Transfer-Encoding: 8bit\\n"
|
Chris@0
|
122 "Plural-Forms: nplurals=2; plural=(n > 1);\\n"
|
Chris@0
|
123
|
Chris@0
|
124 EOF;
|
Chris@0
|
125
|
Chris@0
|
126 // Convert array of translations to Gettext source and translation strings.
|
Chris@0
|
127 if ($translations) {
|
Chris@0
|
128 foreach ($translations as $source => $target) {
|
Chris@0
|
129 $text .= 'msgid "' . $source . '"' . "\n";
|
Chris@0
|
130 $text .= 'msgstr "' . $target . '"' . "\n";
|
Chris@0
|
131 }
|
Chris@0
|
132 }
|
Chris@0
|
133
|
Chris@18
|
134 \Drupal::service('file_system')->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY);
|
Chris@0
|
135 $file = File::create([
|
Chris@0
|
136 'uid' => 1,
|
Chris@0
|
137 'filename' => $filename,
|
Chris@0
|
138 'uri' => $path . '/' . $filename,
|
Chris@0
|
139 'filemime' => 'text/x-gettext-translation',
|
Chris@0
|
140 'timestamp' => $timestamp,
|
Chris@0
|
141 'status' => FILE_STATUS_PERMANENT,
|
Chris@0
|
142 ]);
|
Chris@0
|
143 file_put_contents($file->getFileUri(), $po_header . $text);
|
Chris@14
|
144 touch(\Drupal::service('file_system')->realpath($file->getFileUri()), $timestamp);
|
Chris@0
|
145 $file->save();
|
Chris@0
|
146 }
|
Chris@0
|
147
|
Chris@0
|
148 /**
|
Chris@0
|
149 * Setup the environment containing local and remote translation files.
|
Chris@0
|
150 *
|
Chris@0
|
151 * Update tests require a simulated environment for local and remote files.
|
Chris@0
|
152 * Normally remote files are located at a remote server (e.g. ftp.drupal.org).
|
Chris@0
|
153 * For testing we can not rely on this. A directory in the file system of the
|
Chris@0
|
154 * test site is designated for remote files and is addressed using an absolute
|
Chris@0
|
155 * URL. Because Drupal does not allow files with a po extension to be accessed
|
Chris@0
|
156 * (denied in .htaccess) the translation files get a _po extension. Another
|
Chris@0
|
157 * directory is designated for local translation files.
|
Chris@0
|
158 *
|
Chris@0
|
159 * The environment is set up with the following files. File creation times are
|
Chris@0
|
160 * set to create different variations in test conditions.
|
Chris@0
|
161 * contrib_module_one
|
Chris@0
|
162 * - remote file: timestamp new
|
Chris@0
|
163 * - local file: timestamp old
|
Chris@0
|
164 * contrib_module_two
|
Chris@0
|
165 * - remote file: timestamp old
|
Chris@0
|
166 * - local file: timestamp new
|
Chris@0
|
167 * contrib_module_three
|
Chris@0
|
168 * - remote file: timestamp old
|
Chris@0
|
169 * - local file: timestamp old
|
Chris@0
|
170 * custom_module_one
|
Chris@0
|
171 * - local file: timestamp new
|
Chris@0
|
172 * Time stamp of current translation set by setCurrentTranslations() is always
|
Chris@0
|
173 * timestamp medium. This makes it easy to predict which translation will be
|
Chris@0
|
174 * imported.
|
Chris@0
|
175 */
|
Chris@0
|
176 protected function setTranslationFiles() {
|
Chris@0
|
177 $config = $this->config('locale.settings');
|
Chris@0
|
178
|
Chris@0
|
179 // A flag is set to let the locale_test module replace the project data with
|
Chris@0
|
180 // a set of test projects which match the below project files.
|
Chris@0
|
181 \Drupal::state()->set('locale.test_projects_alter', TRUE);
|
Chris@0
|
182 \Drupal::state()->set('locale.remove_core_project', FALSE);
|
Chris@0
|
183
|
Chris@0
|
184 // Setup the environment.
|
Chris@0
|
185 $public_path = PublicStream::basePath();
|
Chris@0
|
186 $this->setTranslationsDirectory($public_path . '/local');
|
Chris@0
|
187 $config->set('translation.default_filename', '%project-%version.%language._po')->save();
|
Chris@0
|
188
|
Chris@0
|
189 // Setting up sets of translations for the translation files.
|
Chris@0
|
190 $translations_one = ['January' => 'Januar_1', 'February' => 'Februar_1', 'March' => 'Marz_1'];
|
Chris@0
|
191 $translations_two = ['February' => 'Februar_2', 'March' => 'Marz_2', 'April' => 'April_2'];
|
Chris@0
|
192 $translations_three = ['April' => 'April_3', 'May' => 'Mai_3', 'June' => 'Juni_3'];
|
Chris@0
|
193
|
Chris@0
|
194 // Add a number of files to the local file system to serve as remote
|
Chris@0
|
195 // translation server and match the project definitions set in
|
Chris@0
|
196 // locale_test_locale_translation_projects_alter().
|
Chris@0
|
197 $this->makePoFile('remote/8.x/contrib_module_one', 'contrib_module_one-8.x-1.1.de._po', $this->timestampNew, $translations_one);
|
Chris@0
|
198 $this->makePoFile('remote/8.x/contrib_module_two', 'contrib_module_two-8.x-2.0-beta4.de._po', $this->timestampOld, $translations_two);
|
Chris@0
|
199 $this->makePoFile('remote/8.x/contrib_module_three', 'contrib_module_three-8.x-1.0.de._po', $this->timestampOld, $translations_three);
|
Chris@0
|
200
|
Chris@0
|
201 // Add a number of files to the local file system to serve as local
|
Chris@0
|
202 // translation files and match the project definitions set in
|
Chris@0
|
203 // locale_test_locale_translation_projects_alter().
|
Chris@0
|
204 $this->makePoFile('local', 'contrib_module_one-8.x-1.1.de._po', $this->timestampOld, $translations_one);
|
Chris@0
|
205 $this->makePoFile('local', 'contrib_module_two-8.x-2.0-beta4.de._po', $this->timestampNew, $translations_two);
|
Chris@0
|
206 $this->makePoFile('local', 'contrib_module_three-8.x-1.0.de._po', $this->timestampOld, $translations_three);
|
Chris@0
|
207 $this->makePoFile('local', 'custom_module_one.de.po', $this->timestampNew);
|
Chris@0
|
208 }
|
Chris@0
|
209
|
Chris@0
|
210 /**
|
Chris@0
|
211 * Setup existing translations in the database and set up the status of
|
Chris@0
|
212 * existing translations.
|
Chris@0
|
213 */
|
Chris@0
|
214 protected function setCurrentTranslations() {
|
Chris@0
|
215 // Add non customized translations to the database.
|
Chris@0
|
216 $langcode = 'de';
|
Chris@0
|
217 $context = '';
|
Chris@0
|
218 $non_customized_translations = [
|
Chris@0
|
219 'March' => 'Marz',
|
Chris@0
|
220 'June' => 'Juni',
|
Chris@0
|
221 ];
|
Chris@0
|
222 foreach ($non_customized_translations as $source => $translation) {
|
Chris@0
|
223 $string = $this->container->get('locale.storage')->createString([
|
Chris@0
|
224 'source' => $source,
|
Chris@0
|
225 'context' => $context,
|
Chris@0
|
226 ])
|
Chris@0
|
227 ->save();
|
Chris@0
|
228 $this->container->get('locale.storage')->createTranslation([
|
Chris@0
|
229 'lid' => $string->getId(),
|
Chris@0
|
230 'language' => $langcode,
|
Chris@0
|
231 'translation' => $translation,
|
Chris@0
|
232 'customized' => LOCALE_NOT_CUSTOMIZED,
|
Chris@0
|
233 ])->save();
|
Chris@0
|
234 }
|
Chris@0
|
235
|
Chris@0
|
236 // Add customized translations to the database.
|
Chris@0
|
237 $customized_translations = [
|
Chris@0
|
238 'January' => 'Januar_customized',
|
Chris@0
|
239 'February' => 'Februar_customized',
|
Chris@0
|
240 'May' => 'Mai_customized',
|
Chris@0
|
241 ];
|
Chris@0
|
242 foreach ($customized_translations as $source => $translation) {
|
Chris@0
|
243 $string = $this->container->get('locale.storage')->createString([
|
Chris@0
|
244 'source' => $source,
|
Chris@0
|
245 'context' => $context,
|
Chris@0
|
246 ])
|
Chris@0
|
247 ->save();
|
Chris@0
|
248 $this->container->get('locale.storage')->createTranslation([
|
Chris@0
|
249 'lid' => $string->getId(),
|
Chris@0
|
250 'language' => $langcode,
|
Chris@0
|
251 'translation' => $translation,
|
Chris@0
|
252 'customized' => LOCALE_CUSTOMIZED,
|
Chris@0
|
253 ])->save();
|
Chris@0
|
254 }
|
Chris@0
|
255
|
Chris@0
|
256 // Add a state of current translations in locale_files.
|
Chris@0
|
257 $default = [
|
Chris@0
|
258 'langcode' => $langcode,
|
Chris@0
|
259 'uri' => '',
|
Chris@0
|
260 'timestamp' => $this->timestampMedium,
|
Chris@0
|
261 'last_checked' => $this->timestampMedium,
|
Chris@0
|
262 ];
|
Chris@0
|
263 $data[] = [
|
Chris@0
|
264 'project' => 'contrib_module_one',
|
Chris@0
|
265 'filename' => 'contrib_module_one-8.x-1.1.de._po',
|
Chris@0
|
266 'version' => '8.x-1.1',
|
Chris@0
|
267 ];
|
Chris@0
|
268 $data[] = [
|
Chris@0
|
269 'project' => 'contrib_module_two',
|
Chris@0
|
270 'filename' => 'contrib_module_two-8.x-2.0-beta4.de._po',
|
Chris@0
|
271 'version' => '8.x-2.0-beta4',
|
Chris@0
|
272 ];
|
Chris@0
|
273 $data[] = [
|
Chris@0
|
274 'project' => 'contrib_module_three',
|
Chris@0
|
275 'filename' => 'contrib_module_three-8.x-1.0.de._po',
|
Chris@0
|
276 'version' => '8.x-1.0',
|
Chris@0
|
277 ];
|
Chris@0
|
278 $data[] = [
|
Chris@0
|
279 'project' => 'custom_module_one',
|
Chris@0
|
280 'filename' => 'custom_module_one.de.po',
|
Chris@0
|
281 'version' => '',
|
Chris@0
|
282 ];
|
Chris@18
|
283 $connection = Database::getConnection();
|
Chris@0
|
284 foreach ($data as $file) {
|
Chris@0
|
285 $file = array_merge($default, $file);
|
Chris@18
|
286 $connection->insert('locale_file')->fields($file)->execute();
|
Chris@0
|
287 }
|
Chris@0
|
288 }
|
Chris@0
|
289
|
Chris@0
|
290 /**
|
Chris@0
|
291 * Checks the translation of a string.
|
Chris@0
|
292 *
|
Chris@0
|
293 * @param string $source
|
Chris@0
|
294 * Translation source string.
|
Chris@0
|
295 * @param string $translation
|
Chris@0
|
296 * Translation to check. Use empty string to check for a not existing
|
Chris@0
|
297 * translation.
|
Chris@0
|
298 * @param string $langcode
|
Chris@0
|
299 * Language code of the language to translate to.
|
Chris@0
|
300 * @param string $message
|
Chris@0
|
301 * (optional) A message to display with the assertion.
|
Chris@0
|
302 */
|
Chris@0
|
303 protected function assertTranslation($source, $translation, $langcode, $message = '') {
|
Chris@0
|
304 $db_translation = db_query('SELECT translation FROM {locales_target} lt INNER JOIN {locales_source} ls ON ls.lid = lt.lid WHERE ls.source = :source AND lt.language = :langcode', [':source' => $source, ':langcode' => $langcode])->fetchField();
|
Chris@0
|
305 $db_translation = $db_translation == FALSE ? '' : $db_translation;
|
Chris@0
|
306 $this->assertEqual($translation, $db_translation, $message ? $message : format_string('Correct translation of %source (%language)', ['%source' => $source, '%language' => $langcode]));
|
Chris@0
|
307 }
|
Chris@0
|
308
|
Chris@0
|
309 }
|