comparison core/modules/locale/locale.module @ 5:12f9dff5fda9 tip

Update to Drupal core 8.7.1
author Chris Cannam
date Thu, 09 May 2019 15:34:47 +0100
parents a9cd425dd02b
children
comparison
equal deleted inserted replaced
4:a9cd425dd02b 5:12f9dff5fda9
8 * displayed in different languages, and nodes can have languages assigned. The 8 * displayed in different languages, and nodes can have languages assigned. The
9 * setup of languages and translations is completely web based. Gettext portable 9 * setup of languages and translations is completely web based. Gettext portable
10 * object files are supported. 10 * object files are supported.
11 */ 11 */
12 12
13 use Drupal\Component\Gettext\PoItem;
13 use Drupal\Component\Serialization\Json; 14 use Drupal\Component\Serialization\Json;
14 use Drupal\Component\Utility\Html; 15 use Drupal\Component\Utility\Html;
15 use Drupal\Component\Utility\UrlHelper; 16 use Drupal\Component\Utility\UrlHelper;
16 use Drupal\Component\Utility\Xss; 17 use Drupal\Component\Utility\Xss;
18 use Drupal\Core\File\Exception\FileException;
19 use Drupal\Core\File\FileSystemInterface;
17 use Drupal\Core\Url; 20 use Drupal\Core\Url;
18 use Drupal\Core\Asset\AttachedAssetsInterface; 21 use Drupal\Core\Asset\AttachedAssetsInterface;
19 use Drupal\Core\Form\FormStateInterface; 22 use Drupal\Core\Form\FormStateInterface;
20 use Drupal\Core\Routing\RouteMatchInterface; 23 use Drupal\Core\Routing\RouteMatchInterface;
21 use Drupal\Core\Language\LanguageInterface; 24 use Drupal\Core\Language\LanguageInterface;
144 function locale_help($route_name, RouteMatchInterface $route_match) { 147 function locale_help($route_name, RouteMatchInterface $route_match) {
145 switch ($route_name) { 148 switch ($route_name) {
146 case 'help.page.locale': 149 case 'help.page.locale':
147 $output = ''; 150 $output = '';
148 $output .= '<h3>' . t('About') . '</h3>'; 151 $output .= '<h3>' . t('About') . '</h3>';
149 $output .= '<p>' . t('The Interface Translation module allows you to translate interface text (<em>strings</em>) into different languages, and to switch between them for the display of interface text. It uses the functionality provided by the <a href=":language">Language module</a>. For more information, see the <a href=":doc-url">online documentation for the Interface Translation module</a>.', [':doc-url' => 'https://www.drupal.org/documentation/modules/locale/', ':language' => \Drupal::url('help.page', ['name' => 'language'])]) . '</p>'; 152 $output .= '<p>' . t('The Interface Translation module allows you to translate interface text (<em>strings</em>) into different languages, and to switch between them for the display of interface text. It uses the functionality provided by the <a href=":language">Language module</a>. For more information, see the <a href=":doc-url">online documentation for the Interface Translation module</a>.', [':doc-url' => 'https://www.drupal.org/documentation/modules/locale/', ':language' => Url::fromRoute('help.page', ['name' => 'language'])->toString()]) . '</p>';
150 $output .= '<h3>' . t('Uses') . '</h3>'; 153 $output .= '<h3>' . t('Uses') . '</h3>';
151 $output .= '<dl>'; 154 $output .= '<dl>';
152 $output .= '<dt>' . t('Importing translation files') . '</dt>'; 155 $output .= '<dt>' . t('Importing translation files') . '</dt>';
153 $output .= '<dd>' . t('Translation files with translated interface text are imported automatically when languages are added on the <a href=":languages">Languages</a> page, or when modules or themes are enabled. On the <a href=":locale-settings">Interface translation settings</a> page, the <em>Translation source</em> can be restricted to local files only, or to include the <a href=":server">Drupal translation server</a>. Although modules and themes may not be fully translated in all languages, new translations become available frequently. You can specify whether and how often to check for translation file updates and whether to overwrite existing translations on the <a href=":locale-settings">Interface translation settings</a> page. You can also manually import a translation file on the <a href=":import">Interface translation import</a> page.', [':import' => \Drupal::url('locale.translate_import'), ':locale-settings' => \Drupal::url('locale.settings'), ':languages' => \Drupal::url('entity.configurable_language.collection'), ':server' => 'https://localize.drupal.org']) . '</dd>'; 156 $output .= '<dd>' . t('Translation files with translated interface text are imported automatically when languages are added on the <a href=":languages">Languages</a> page, or when modules or themes are enabled. On the <a href=":locale-settings">Interface translation settings</a> page, the <em>Translation source</em> can be restricted to local files only, or to include the <a href=":server">Drupal translation server</a>. Although modules and themes may not be fully translated in all languages, new translations become available frequently. You can specify whether and how often to check for translation file updates and whether to overwrite existing translations on the <a href=":locale-settings">Interface translation settings</a> page. You can also manually import a translation file on the <a href=":import">Interface translation import</a> page.', [':import' => Url::fromRoute('locale.translate_import')->toString(), ':locale-settings' => Url::fromRoute('locale.settings')->toString(), ':languages' => Url::fromRoute('entity.configurable_language.collection')->toString(), ':server' => 'https://localize.drupal.org']) . '</dd>';
154 $output .= '<dt>' . t('Checking the translation status') . '</dt>'; 157 $output .= '<dt>' . t('Checking the translation status') . '</dt>';
155 $output .= '<dd>' . t('You can check how much of the interface on your site is translated into which language on the <a href=":languages">Languages</a> page. On the <a href=":translation-updates">Available translation updates</a> page, you can check whether interface translation updates are available on the <a href=":server">Drupal translation server</a>.', [':languages' => \Drupal::url('entity.configurable_language.collection'), ':translation-updates' => \Drupal::url('locale.translate_status'), ':server' => 'https://localize.drupal.org']) . '<dd>'; 158 $output .= '<dd>' . t('You can check how much of the interface on your site is translated into which language on the <a href=":languages">Languages</a> page. On the <a href=":translation-updates">Available translation updates</a> page, you can check whether interface translation updates are available on the <a href=":server">Drupal translation server</a>.', [':languages' => Url::fromRoute('entity.configurable_language.collection')->toString(), ':translation-updates' => Url::fromRoute('locale.translate_status')->toString(), ':server' => 'https://localize.drupal.org']) . '<dd>';
156 $output .= '<dt>' . t('Translating individual strings') . '</dt>'; 159 $output .= '<dt>' . t('Translating individual strings') . '</dt>';
157 $output .= '<dd>' . t('You can translate individual strings directly on the <a href=":translate">User interface translation</a> page, or download the currently-used translation file for a specific language on the <a href=":export">Interface translation export</a> page. Once you have edited the translation file, you can then import it again on the <a href=":import">Interface translation import</a> page.', [':translate' => \Drupal::url('locale.translate_page'), ':export' => \Drupal::url('locale.translate_export'), ':import' => \Drupal::url('locale.translate_import')]) . '</dd>'; 160 $output .= '<dd>' . t('You can translate individual strings directly on the <a href=":translate">User interface translation</a> page, or download the currently-used translation file for a specific language on the <a href=":export">Interface translation export</a> page. Once you have edited the translation file, you can then import it again on the <a href=":import">Interface translation import</a> page.', [':translate' => Url::fromRoute('locale.translate_page')->toString(), ':export' => Url::fromRoute('locale.translate_export')->toString(), ':import' => Url::fromRoute('locale.translate_import')->toString()]) . '</dd>';
158 $output .= '<dt>' . t('Overriding default English strings') . '</dt>'; 161 $output .= '<dt>' . t('Overriding default English strings') . '</dt>';
159 $output .= '<dd>' . t('If translation is enabled for English, you can <em>override</em> the default English interface text strings in your site with other English text strings on the <a href=":translate">User interface translation</a> page. Translation is off by default for English, but you can turn it on by visiting the <em>Edit language</em> page for <em>English</em> from the <a href=":languages">Languages</a> page.', [':translate' => \Drupal::url('locale.translate_page'), ':languages' => \Drupal::url('entity.configurable_language.collection')]) . '</dd>'; 162 $output .= '<dd>' . t('If translation is enabled for English, you can <em>override</em> the default English interface text strings in your site with other English text strings on the <a href=":translate">User interface translation</a> page. Translation is off by default for English, but you can turn it on by visiting the <em>Edit language</em> page for <em>English</em> from the <a href=":languages">Languages</a> page.', [':translate' => Url::fromRoute('locale.translate_page')->toString(), ':languages' => Url::fromRoute('entity.configurable_language.collection')->toString()]) . '</dd>';
160 $output .= '</dl>'; 163 $output .= '</dl>';
161 return $output; 164 return $output;
162 165
163 case 'entity.configurable_language.collection': 166 case 'entity.configurable_language.collection':
164 return '<p>' . t('Interface translations are automatically imported when a language is added, or when new modules or themes are enabled. The report <a href=":update">Available translation updates</a> shows the status. Interface text can be customized in the <a href=":translate">user interface translation</a> page.', [':update' => \Drupal::url('locale.translate_status'), ':translate' => \Drupal::url('locale.translate_page')]) . '</p>'; 167 return '<p>' . t('Interface translations are automatically imported when a language is added, or when new modules or themes are enabled. The report <a href=":update">Available translation updates</a> shows the status. Interface text can be customized in the <a href=":translate">user interface translation</a> page.', [':update' => Url::fromRoute('locale.translate_status')->toString(), ':translate' => Url::fromRoute('locale.translate_page')->toString()]) . '</p>';
165 168
166 case 'locale.translate_page': 169 case 'locale.translate_page':
167 $output = '<p>' . t('This page allows a translator to search for specific translated and untranslated strings, and is used when creating or editing translations. (Note: Because translation tasks involve many strings, it may be more convenient to <a title="User interface translation export" href=":export">export</a> strings for offline editing in a desktop Gettext translation editor.) Searches may be limited to strings in a specific language.', [':export' => \Drupal::url('locale.translate_export')]) . '</p>'; 170 $output = '<p>' . t('This page allows a translator to search for specific translated and untranslated strings, and is used when creating or editing translations. (Note: Because translation tasks involve many strings, it may be more convenient to <a title="User interface translation export" href=":export">export</a> strings for offline editing in a desktop Gettext translation editor.) Searches may be limited to strings in a specific language.', [':export' => Url::fromRoute('locale.translate_export')->toString()]) . '</p>';
168 return $output; 171 return $output;
169 172
170 case 'locale.translate_import': 173 case 'locale.translate_import':
171 $output = '<p>' . t('Translation files are automatically downloaded and imported when <a title="Languages" href=":language">languages</a> are added, or when modules or themes are enabled.', [':language' => \Drupal::url('entity.configurable_language.collection')]) . '</p>'; 174 $output = '<p>' . t('Translation files are automatically downloaded and imported when <a title="Languages" href=":language">languages</a> are added, or when modules or themes are enabled.', [':language' => Url::fromRoute('entity.configurable_language.collection')->toString()]) . '</p>';
172 $output .= '<p>' . t('This page allows translators to manually import translated strings contained in a Gettext Portable Object (.po) file. Manual import may be used for customized translations or for the translation of custom modules and themes. To customize translations you can download a translation file from the <a href=":url">Drupal translation server</a> or <a title="User interface translation export" href=":export">export</a> translations from the site, customize the translations using a Gettext translation editor, and import the result using this page.', [':url' => 'https://localize.drupal.org', ':export' => \Drupal::url('locale.translate_export')]) . '</p>'; 175 $output .= '<p>' . t('This page allows translators to manually import translated strings contained in a Gettext Portable Object (.po) file. Manual import may be used for customized translations or for the translation of custom modules and themes. To customize translations you can download a translation file from the <a href=":url">Drupal translation server</a> or <a title="User interface translation export" href=":export">export</a> translations from the site, customize the translations using a Gettext translation editor, and import the result using this page.', [':url' => 'https://localize.drupal.org', ':export' => Url::fromRoute('locale.translate_export')->toString()]) . '</p>';
173 $output .= '<p>' . t('Note that importing large .po files may take several minutes.') . '</p>'; 176 $output .= '<p>' . t('Note that importing large .po files may take several minutes.') . '</p>';
174 return $output; 177 return $output;
175 178
176 case 'locale.translate_export': 179 case 'locale.translate_export':
177 return '<p>' . t('This page exports the translated strings used by your site. An export file may be in Gettext Portable Object (<em>.po</em>) form, which includes both the original string and the translation (used to share translations with others), or in Gettext Portable Object Template (<em>.pot</em>) form, which includes the original strings only (used to create new translations with a Gettext translation editor).') . '</p>'; 180 return '<p>' . t('This page exports the translated strings used by your site. An export file may be in Gettext Portable Object (<em>.po</em>) form, which includes both the original string and the translation (used to share translations with others), or in Gettext Portable Object Template (<em>.pot</em>) form, which includes the original strings only (used to create new translations with a Gettext translation editor).') . '</p>';
834 * 837 *
835 * @return int 838 * @return int
836 * FALSE on failure. Otherwise SAVED_NEW or SAVED_UPDATED. 839 * FALSE on failure. Otherwise SAVED_NEW or SAVED_UPDATED.
837 */ 840 */
838 function locale_translation_update_file_history($file) { 841 function locale_translation_update_file_history($file) {
839 $status = db_merge('locale_file') 842 $status = \Drupal::database()->merge('locale_file')
840 ->key([ 843 ->key([
841 'project' => $file->project, 844 'project' => $file->project,
842 'langcode' => $file->langcode, 845 'langcode' => $file->langcode,
843 ]) 846 ])
844 ->fields([ 847 ->fields([
861 * language code(s) are specified the conditions will be ANDed. 864 * language code(s) are specified the conditions will be ANDed.
862 * @param array $langcodes 865 * @param array $langcodes
863 * Language code(s) to be deleted from the file history. 866 * Language code(s) to be deleted from the file history.
864 */ 867 */
865 function locale_translation_file_history_delete($projects = [], $langcodes = []) { 868 function locale_translation_file_history_delete($projects = [], $langcodes = []) {
866 $query = db_delete('locale_file'); 869 $query = \Drupal::database()->delete('locale_file');
867 if (!empty($projects)) { 870 if (!empty($projects)) {
868 $query->condition('project', $projects, 'IN'); 871 $query->condition('project', $projects, 'IN');
869 } 872 }
870 if (!empty($langcodes)) { 873 if (!empty($langcodes)) {
871 $query->condition('langcode', $langcodes, 'IN'); 874 $query->condition('langcode', $langcodes, 'IN');
1148 \s*.+?\s*,\s* # match count argument 1151 \s*.+?\s*,\s* # match count argument
1149 (' . LOCALE_JS_STRING . ')\s*,\s* # match singular string argument 1152 (' . LOCALE_JS_STRING . ')\s*,\s* # match singular string argument
1150 ( # capture plural string argument 1153 ( # capture plural string argument
1151 (?: # non-capturing group to repeat string pieces 1154 (?: # non-capturing group to repeat string pieces
1152 (?: 1155 (?:
1153 \' # match start of single-quoted string 1156 \'(?:\\\\\'|[^\'])*\' # match single-quoted string with any character except unescaped single-quote
1154 (?:\\\\\'|[^\'])* # match any character except unescaped single-quote
1155 @count # match "@count"
1156 (?:\\\\\'|[^\'])* # match any character except unescaped single-quote
1157 \' # match end of single-quoted string
1158 | 1157 |
1159 " # match start of double-quoted string 1158 "(?:\\\\"|[^"])*" # match double-quoted string with any character except unescaped double-quote
1160 (?:\\\\"|[^"])* # match any character except unescaped double-quote
1161 @count # match "@count"
1162 (?:\\\\"|[^"])* # match any character except unescaped double-quote
1163 " # match end of double-quoted string
1164 ) 1159 )
1165 (?:\s*\+\s*)? # match "+" with possible whitespace, for str concat 1160 (?:\s*\+\s*)? # match "+" with possible whitespace, for str concat
1166 )+ # match multiple because we supports concatenating strs 1161 )+ # match multiple because we supports concatenating strs
1167 )\s* # end capturing of plural string argument 1162 )\s* # end capturing of plural string argument
1168 (?:,\s*' . LOCALE_JS_OBJECT . '\s* # optionally capture string args 1163 (?:,\s*' . LOCALE_JS_OBJECT . '\s* # optionally capture string args
1182 } 1177 }
1183 1178
1184 // Add string from Drupal.formatPlural(). 1179 // Add string from Drupal.formatPlural().
1185 foreach ($plural_matches[1] as $key => $string) { 1180 foreach ($plural_matches[1] as $key => $string) {
1186 $matches[] = [ 1181 $matches[] = [
1187 'source' => _locale_strip_quotes($string) . LOCALE_PLURAL_DELIMITER . _locale_strip_quotes($plural_matches[2][$key]), 1182 'source' => _locale_strip_quotes($string) . PoItem::DELIMITER . _locale_strip_quotes($plural_matches[2][$key]),
1188 'context' => _locale_strip_quotes($plural_matches[3][$key]), 1183 'context' => _locale_strip_quotes($plural_matches[3][$key]),
1189 ]; 1184 ];
1190 } 1185 }
1191 1186
1192 // Loop through all matches and process them. 1187 // Loop through all matches and process them.
1295 1290
1296 // Delete old file, if we have no translations anymore, or a different file to 1291 // Delete old file, if we have no translations anymore, or a different file to
1297 // be saved. 1292 // be saved.
1298 $locale_javascripts = \Drupal::state()->get('locale.translation.javascript') ?: []; 1293 $locale_javascripts = \Drupal::state()->get('locale.translation.javascript') ?: [];
1299 $changed_hash = !isset($locale_javascripts[$language->getId()]) || ($locale_javascripts[$language->getId()] != $data_hash); 1294 $changed_hash = !isset($locale_javascripts[$language->getId()]) || ($locale_javascripts[$language->getId()] != $data_hash);
1295
1296 /** @var \Drupal\Core\File\FileSystemInterface $file_system */
1297 $file_system = \Drupal::service('file_system');
1298
1300 if (!empty($locale_javascripts[$language->getId()]) && (!$data || $changed_hash)) { 1299 if (!empty($locale_javascripts[$language->getId()]) && (!$data || $changed_hash)) {
1301 file_unmanaged_delete($dir . '/' . $language->getId() . '_' . $locale_javascripts[$language->getId()] . '.js'); 1300 try {
1301 $file_system->delete($dir . '/' . $language->getId() . '_' . $locale_javascripts[$language->getId()] . '.js');
1302 }
1303 catch (FileException $e) {
1304 // Ignore.
1305 }
1302 $locale_javascripts[$language->getId()] = ''; 1306 $locale_javascripts[$language->getId()] = '';
1303 $status = 'deleted'; 1307 $status = 'deleted';
1304 } 1308 }
1305 1309
1306 // Only create a new file if the content has changed or the original file got 1310 // Only create a new file if the content has changed or the original file got
1307 // lost. 1311 // lost.
1308 $dest = $dir . '/' . $language->getId() . '_' . $data_hash . '.js'; 1312 $dest = $dir . '/' . $language->getId() . '_' . $data_hash . '.js';
1309 if ($data && ($changed_hash || !file_exists($dest))) { 1313 if ($data && ($changed_hash || !file_exists($dest))) {
1310 // Ensure that the directory exists and is writable, if possible. 1314 // Ensure that the directory exists and is writable, if possible.
1311 file_prepare_directory($dir, FILE_CREATE_DIRECTORY); 1315 $file_system->prepareDirectory($dir, FileSystemInterface::CREATE_DIRECTORY);
1312 1316
1313 // Save the file. 1317 // Save the file.
1314 if (file_unmanaged_save_data($data, $dest)) { 1318 try {
1315 $locale_javascripts[$language->getId()] = $data_hash; 1319 if ($file_system->saveData($data, $dest)) {
1316 // If we deleted a previous version of the file and we replace it with a 1320 $locale_javascripts[$language->getId()] = $data_hash;
1317 // new one we have an update. 1321 // If we deleted a previous version of the file and we replace it with a
1318 if ($status == 'deleted') { 1322 // new one we have an update.
1319 $status = 'updated'; 1323 if ($status == 'deleted') {
1324 $status = 'updated';
1325 }
1326 // If the file did not exist previously and the data has changed we have
1327 // a fresh creation.
1328 elseif ($changed_hash) {
1329 $status = 'created';
1330 }
1331 // If the data hash is unchanged the translation was lost and has to be
1332 // rebuilt.
1333 else {
1334 $status = 'rebuilt';
1335 }
1320 } 1336 }
1321 // If the file did not exist previously and the data has changed we have 1337 else {
1322 // a fresh creation. 1338 $locale_javascripts[$language->getId()] = '';
1323 elseif ($changed_hash) { 1339 $status = 'error';
1324 $status = 'created';
1325 } 1340 }
1326 // If the data hash is unchanged the translation was lost and has to be 1341 }
1327 // rebuilt. 1342 catch (FileException $e) {
1328 else { 1343 // Do nothing.
1329 $status = 'rebuilt';
1330 }
1331 }
1332 else {
1333 $locale_javascripts[$language->getId()] = '';
1334 $status = 'error';
1335 } 1344 }
1336 } 1345 }
1337 1346
1338 // Save the new JavaScript hash (or an empty value if the file just got 1347 // Save the new JavaScript hash (or an empty value if the file just got
1339 // deleted). Act only if some operation was executed that changed the hash 1348 // deleted). Act only if some operation was executed that changed the hash