3 namespace Drush\Drupal\Commands\core;
5 use Consolidation\AnnotatedCommand\CommandData;
6 use Drupal\Component\Gettext\PoStreamWriter;
7 use Drupal\Core\Config\ConfigFactoryInterface;
8 use Drupal\Core\Extension\ModuleHandlerInterface;
9 use Drupal\Core\Language\LanguageInterface;
10 use Drupal\Core\Language\LanguageManagerInterface;
11 use Drupal\Core\State\StateInterface;
12 use Drupal\language\Entity\ConfigurableLanguage;
13 use Drupal\locale\PoDatabaseReader;
14 use Drush\Commands\DrushCommands;
16 class LocaleCommands extends DrushCommands
19 protected $languageManager;
21 protected $configFactory;
23 protected $moduleHandler;
28 * @return \Drupal\Core\Language\LanguageManagerInterface
30 protected function getLanguageManager()
32 return $this->languageManager;
36 * @return \Drupal\Core\Config\ConfigFactoryInterface
38 protected function getConfigFactory()
40 return $this->configFactory;
44 * @return \Drupal\Core\Extension\ModuleHandlerInterface
46 public function getModuleHandler()
48 return $this->moduleHandler;
54 public function getState()
59 public function __construct(LanguageManagerInterface $languageManager, ConfigFactoryInterface $configFactory, ModuleHandlerInterface $moduleHandler, StateInterface $state)
61 $this->languageManager = $languageManager;
62 $this->configFactory = $configFactory;
63 $this->moduleHandler = $moduleHandler;
64 $this->state = $state;
68 * Checks for available translation updates.
70 * @command locale:check
71 * @aliases locale-check
72 * @validate-module-enabled locale
74 public function check()
76 $this->getModuleHandler()->loadInclude('locale', 'inc', 'locale.compare');
78 // Check translation status of all translatable project in all languages.
79 // First we clear the cached list of projects. Although not strictly
80 // necessary, this is helpful in case the project list is out of sync.
81 locale_translation_flush_projects();
82 locale_translation_check_projects();
84 // Execute a batch if required. A batch is only used when remote files
87 drush_backend_batch_process();
92 * Imports the available translation updates.
94 * @see TranslationStatusForm::buildForm()
95 * @see TranslationStatusForm::prepareUpdateData()
96 * @see TranslationStatusForm::submitForm()
98 * @todo This can be simplified once https://www.drupal.org/node/2631584 lands
101 * @command locale:update
102 * @aliases locale-update
103 * @option langcodes A comma-separated list of language codes to update. If omitted, all translations will be updated.
104 * @validate-module-enabled locale
106 public function update($options = ['langcodes' => self::REQ])
108 $module_handler = $this->getModuleHandler();
109 $module_handler->loadInclude('locale', 'fetch.inc');
110 $module_handler->loadInclude('locale', 'bulk.inc');
113 foreach (locale_translation_get_status() as $project_id => $project) {
114 foreach ($project as $langcode => $project_info) {
115 if (!empty($project_info->type) && !in_array($langcode, $langcodes)) {
116 $langcodes[] = $langcode;
121 if ($passed_langcodes = $options['langcodes']) {
122 $langcodes = array_intersect($langcodes, explode(',', $passed_langcodes));
123 // @todo Not selecting any language code in the user interface results in
124 // all translations being updated, so we mimick that behavior here.
127 // Deduplicate the list of langcodes since each project may have added the
128 // same language several times.
129 $langcodes = array_unique($langcodes);
131 // @todo Restricting by projects is not possible in the user interface and is
132 // broken when attempting to do it in a hook_form_alter() implementation so
133 // we do not allow for it here either.
136 // Set the translation import options. This determines if existing
137 // translations will be overwritten by imported strings.
138 $translationOptions = _locale_translation_default_update_options();
140 // If the status was updated recently we can immediately start fetching the
141 // translation updates. If the status is expired we clear it an run a batch to
142 // update the status and then fetch the translation updates.
143 $last_checked = $this->getState()->get('locale.translation_last_checked');
144 if ($last_checked < REQUEST_TIME - LOCALE_TRANSLATION_STATUS_TTL) {
145 locale_translation_clear_status();
146 $batch = locale_translation_batch_update_build([], $langcodes, $translationOptions);
149 // Set a batch to download and import translations.
150 $batch = locale_translation_batch_fetch_build($projects, $langcodes, $translationOptions);
152 // Set a batch to update configuration as well.
153 if ($batch = locale_config_batch_update_components($translationOptions, $langcodes)) {
158 drush_backend_batch_process();
162 * Imports to a gettext translation file.
164 * @command locale:import
165 * @validate-module-enabled locale
166 * @param $langcode The language code of the imported translations.
167 * @param $file Path and file name of the gettext file.
168 * @option type The type of translations to be imported, defaults to 'not-customized'. Options:
169 * - customized: Treat imported strings as custom translations.
170 * - not-customized: Treat imported strings as not-custom translations.
171 * @option override Whether and how imported strings will override existing translations. Defaults to the Import behavior configurred in the admin interface. Options:
172 * - none: Don't overwrite existing translations. Only append new translations.
173 * - customized: Only override existing customized translations.
174 * - not-customized: Only override non-customized translations, customized translations are kept.
175 * - all: Override any existing translation.
176 * @usage drush locale-import nl drupal-8.4.2.nl.po
177 * Import the Dutch drupal core translation.
178 * @usage drush locale-import nl custom-translations.po --type=custom --override=all
179 * Import customized Dutch translations and override any existing translation.
180 * @aliases locale-export
183 public function import($langcode, $file, $options = ['type' => self::OPT, 'override' => self::OPT])
185 if (!drush_file_not_empty($file)) {
186 throw new \Exception(dt('File @file not found or empty.', ['@file' => $file]));
189 $language = $this->getTranslatableLanguage($langcode, true);
191 $this->getModuleHandler()->loadInclude('locale', 'translation.inc');
192 $this->getModuleHandler()->loadInclude('locale', 'bulk.inc');
194 $translationOptions = _locale_translation_default_update_options();
195 $translationOptions['langcode'] = $language->getId();
196 $translationOptions['customized'] = $this->convertCustomizedType($options['type']);
197 $override = $this->convertOverrideOption($options['override']);
199 $translationOptions['overwrite_options'] = $override;
203 'filename' => basename($file),
206 $poFile = locale_translate_file_attach_properties($poFile, $translationOptions);
208 // Set a batch to download and import translations.
209 $batch = locale_translate_batch_build([$poFile->uri => $poFile], $translationOptions);
211 if ($batch = locale_config_batch_update_components($translationOptions, [$language->getId()])) {
215 drush_backend_batch_process();
219 * Converts input of translation type.
224 private function convertCustomizedType($type)
228 $result = LOCALE_CUSTOMIZED;
232 $result = LOCALE_NOT_CUSTOMIZED;
240 * Converts input of override option.
245 private function convertOverrideOption($override)
252 'not_customized' => false,
253 'customized' => false,
259 'not_customized' => false,
260 'customized' => true,
264 case 'not-customized':
266 'not_customized' => true,
267 'customized' => false,
273 'not_customized' => true,
274 'customized' => true,
283 * Get translatable language object.
285 * @param string $langcode The language code of the language object.
286 * @param bool $addLanguage Create language when not available.
287 * @return LanguageInterface|null
290 private function getTranslatableLanguage($langcode, $addLanguage = false)
296 $language = $this->getLanguageManager()->getLanguage($langcode);
300 $language = ConfigurableLanguage::createFromLangcode($langcode);
303 $this->logger->success(dt('Added language @language', [
304 '@language' => $language->label(),
307 throw new \Exception(dt('Language code @langcode is not configured.', [
308 '@langcode' => $langcode,
313 if (!$this->isTranslatable($language)) {
314 throw new \Exception(dt('Language code @langcode is not translatable.', [
315 '@langcode' => $langcode,
323 * Check if language is translatable.
325 * @param LanguageInterface $language
328 private function isTranslatable(LanguageInterface $language)
330 if ($language->isLocked()) {
334 if ($language->getId() != 'en') {
338 return (bool)$this->getConfigFactory()
339 ->get('locale.settings')
340 ->get('translate_english');