Commit 37445fd7 authored by catch's avatar catch

Issue #1813762 by ParisLiakos, Jose Reyero, YesCT, japicoder, attiks:...

Issue #1813762 by ParisLiakos, Jose Reyero, YesCT, japicoder, attiks: Introduce unified interfaces, use dependency injection for interface translation.
parent cf622fd6
......@@ -170,6 +170,12 @@ services:
arguments: ['@event_dispatcher', '@service_container', '@controller_resolver']
language_manager:
class: Drupal\Core\Language\LanguageManager
string_translator.custom_strings:
class: Drupal\Core\StringTranslation\Translator\CustomStrings
tags:
- { name: string_translator, priority: 30 }
string_translation:
class: Drupal\Core\StringTranslation\TranslationManager
database.slave:
class: Drupal\Core\Database\Connection
factory_class: Drupal\Core\Database\Database
......@@ -376,7 +382,7 @@ services:
class: Drupal\Core\EventSubscriber\LanguageRequestSubscriber
tags:
- { name: event_subscriber }
arguments: ['@language_manager']
arguments: ['@language_manager', '@string_translation']
exception_controller:
class: Drupal\Core\Controller\ExceptionController
arguments: ['@content_negotiation']
......
......@@ -1389,37 +1389,7 @@ function bootstrap_hooks() {
* @ingroup sanitization
*/
function t($string, array $args = array(), array $options = array()) {
static $custom_strings;
// Merge in default.
if (empty($options['langcode'])) {
$options['langcode'] = language(Language::TYPE_INTERFACE)->langcode;
}
if (empty($options['context'])) {
$options['context'] = '';
}
// First, check for an array of customized strings. If present, use the array
// *instead of* database lookups. This is a high performance way to provide a
// handful of string replacements. See settings.php for examples.
// Cache the $custom_strings variable to improve performance.
if (!isset($custom_strings[$options['langcode']])) {
$custom_strings[$options['langcode']] = variable_get('locale_custom_strings_' . $options['langcode'], array());
}
// Custom strings work for English too, even if locale module is disabled.
if (isset($custom_strings[$options['langcode']][$options['context']][$string])) {
$string = $custom_strings[$options['langcode']][$options['context']][$string];
}
// Translate with locale module if enabled.
elseif ($options['langcode'] != Language::LANGCODE_SYSTEM && ($options['langcode'] != 'en' || variable_get('locale_translate_english', FALSE)) && function_exists('locale')) {
$string = locale($string, $options['context'], $options['langcode']);
}
if (empty($args)) {
return $string;
}
else {
return format_string($string, $args);
}
return Drupal::translation()->translate($string, $args, $options);
}
/**
......@@ -2434,27 +2404,23 @@ function drupal_installation_attempted() {
* Use st() if your code will only run during installation and never any other
* time. Use get_t() if your code could run in either circumstance.
*
* @todo Remove this in favor of t().
*
* @see t()
* @see st()
* @ingroup sanitization
*/
function get_t() {
static $t;
// This is not converted to drupal_static because there is no point in
// resetting this as it can not change in the course of a request.
if (!isset($t)) {
$t = drupal_installation_attempted() ? 'st' : 't';
}
return $t;
return 't';
}
/**
* Initializes all the defined language types.
*
* @see language()
* Initializes all the defined language types and sets the default langcode.
*/
function drupal_language_initialize() {
drupal_container()->get('language_manager')->init();
$language_manager = Drupal::service('language_manager');
$language_manager->init();
Drupal::translation()->setDefaultLangcode($language_manager->getLanguage(Language::TYPE_INTERFACE)->langcode);
}
/**
......@@ -2464,19 +2430,12 @@ function drupal_language_initialize() {
*
* @param string $type
* The type of language object needed, e.g. Language::TYPE_INTERFACE.
*
* @deprecated as of Drupal 8.0. Use
* Drupal::service('language_manager')->getLanguage($type).
*/
function language($type) {
$container = drupal_container();
if (!$container) {
return language_default();
}
if (!$container->has('language_manager')) {
// This happens in rare situations when the container has not been built by
// a kernel and has no services, e.g., when t() is called during unit tests
// for assertions.
$container->register('language_manager', 'Drupal\Core\Language\LanguageManager');
}
return $container->get('language_manager')->getLanguage($type);
return Drupal::service('language_manager')->getLanguage($type);
}
/**
......
......@@ -8,6 +8,7 @@
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Install\TaskException;
use Drupal\Core\Language\Language;
use Drupal\Core\StringTranslation\Translator\FileTranslation;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
......@@ -370,6 +371,12 @@ function install_begin_request(&$install_state) {
// Register the 'language_manager' service.
$container->register('language_manager', 'Drupal\Core\Language\LanguageManager');
// Register the translation services.
$container->register('string_translator.custom_strings', 'Drupal\Core\StringTranslation\Translator\CustomStrings')
->addTag('string_translator');
$container->register('string_translation', 'Drupal\Core\StringTranslation\TranslationManager');
foreach (array('bootstrap', 'config', 'cache', 'menu', 'page', 'path') as $bin) {
$container
->register("cache.$bin", 'Drupal\Core\Cache\MemoryBackend')
......@@ -417,6 +424,12 @@ function install_begin_request(&$install_state) {
// Set up $language, so t() caller functions will still work.
drupal_language_initialize();
// Append file translation to the translation chain.
Drupal::translation()->addTranslator(install_file_translation_service());
// Add in installation language if present.
if (isset($install_state['parameters']['langcode'])) {
Drupal::translation()->setDefaultLangcode($install_state['parameters']['langcode']);
}
require_once __DIR__ . '/ajax.inc';
......@@ -1371,7 +1384,7 @@ function install_select_profile_form($form, &$form_state, $install_state) {
*/
function install_find_translations() {
$translations = array();
$files = install_find_translation_files();
$files = install_file_translation_service()->findTranslationFiles();
// English does not need a translation file.
array_unshift($files, (object) array('name' => 'en'));
foreach ($files as $uri => $file) {
......@@ -1387,37 +1400,24 @@ function install_find_translations() {
}
/**
* Finds installer translations either for a specific langcode or all languages.
* Build a file translation service for installation.
*
* @param $langcode
* (optional) The language code corresponding to the language for which we
* want to find translation files. If omitted, information on all available
* files will be returned.
*
* @return
* An associative array of file information objects keyed by file URIs as
* returned by file_scan_directory().
*
* @see file_scan_directory()
* @return Drupal\Core\StringTranslation\Translator\FileTranslation
* File translation service for the installer.
*/
function install_find_translation_files($langcode = NULL) {
return file_scan_directory(install_translation_directory(), '!drupal-\d+\.\d+\.' . (!empty($langcode) ? preg_quote($langcode, '!') : '[^\.]+') . '\.po$!', array('recurse' => FALSE));
}
function install_file_translation_service() {
static $translation;
/**
* Get installation translations directory path.
*
* @return string
* A string containing the installation translations directory path.
*/
function install_translation_directory() {
if (isset($GLOBALS['conf']['locale.settings']['translation.path'])) {
$directory = $GLOBALS['conf']['locale.settings']['translation.path'];
}
else {
$directory = conf_path() . '/files/translations';
if (!isset($translation)) {
if (isset($GLOBALS['conf']['locale.settings']['translation.path'])) {
$directory = $GLOBALS['conf']['locale.settings']['translation.path'];
}
else {
$directory = conf_path() . '/files/translations';
}
$translation = new FileTranslation($directory);
}
return $directory;
return $translation;
}
/**
......
......@@ -8,7 +8,8 @@
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Database\Database;
use Drupal\Core\DrupalKernel;
use Drupal\locale\Gettext;
use Drupal\Core\StringTranslation\TranslationManager;
use Drupal\Core\StringTranslation\Translator\CustomStrings;
/**
* Requirement severity -- Informational message only.
......@@ -941,48 +942,28 @@ function drupal_requirements_url($severity) {
* @ingroup sanitization
*/
function st($string, array $args = array(), array $options = array()) {
static $strings = NULL;
global $install_state;
static $install_translation;
if (empty($options['context'])) {
$options['context'] = '';
// This may be invoked before the container has been initialized.
$container = Drupal::getContainer();
if ($container && $container->has('translation')) {
// Since the container is properly initialized, we can use standard translation.
return t($string, $args, $options);
}
if (!isset($strings)) {
$strings = array();
if (isset($install_state['parameters']['langcode'])) {
// If the given langcode was selected, there should be at least one .po
// file with its name in the pattern drupal-$version.$langcode.po.
// This might or might not be the entire filename. It is also possible
// that multiple files end with the same suffix, even if unlikely.
$files = install_find_translation_files($install_state['parameters']['langcode']);
if (!empty($files)) {
// Register locale classes with the classloader. Locale module is not
// yet enabled at this stage, so this is not happening automatically.
drupal_classloader_register('locale', drupal_get_path('module', 'locale'));
$strings = Gettext::filesToArray($install_state['parameters']['langcode'], $files);
elseif (drupal_installation_attempted() && isset($install_state['parameters']['langcode'])) {
// The translation service is not there yet, use a temporary one.
if (!isset($install_translation)) {
$install_translation = new TranslationManager();
foreach (array(new CustomStrings(), install_file_translation_service()) as $translator) {
$install_translation->addTranslator($translator);
}
$install_translation->setDefaultLangcode($install_state['parameters']['langcode']);
}
return $install_translation->translate($string, $args, $options);
}
require_once __DIR__ . '/theme.inc';
// Transform arguments before inserting them
foreach ($args as $key => $value) {
switch ($key[0]) {
// Escaped only
case '@':
$args[$key] = check_plain($value);
break;
// Escaped and placeholder
case '%':
default:
$args[$key] = '<em>' . check_plain($value) . '</em>';
break;
// Pass-through
case '!':
}
}
return strtr((!empty($strings[$options['context']][$string]) ? $strings[$options['context']][$string] : $string), $args);
// Just return an untraslated string.
return format_string($string, $args);
}
/**
......
......@@ -387,7 +387,6 @@ function update_prepare_d8_bootstrap() {
$disabled_modules->save();
$theme_config->save();
$disabled_themes->save();
Drupal::service('kernel')->updateModules($sorted_with_filenames, $sorted_with_filenames);
// Migrate the private key to state. This is used to create the token for
// the upgrade batch so needs to be be done before the upgrade has begun.
......@@ -400,6 +399,9 @@ function update_prepare_d8_bootstrap() {
update_prepare_stored_includes();
// Update the environment for the language bootstrap if needed.
update_prepare_d8_language();
// Rebuild kernel after new language fields are added in the database
// because the translation service depends on them being there.
Drupal::service('kernel')->updateModules($sorted_with_filenames, $sorted_with_filenames);
// Change language column to langcode in url_alias.
if (db_table_exists('url_alias') && db_field_exists('url_alias', 'language')) {
......
......@@ -371,4 +371,14 @@ public static function urlGenerator() {
return static::$container->get('url_generator');
}
/**
* Returns the string translation service.
*
* @return \Drupal\Core\Translation\TranslationManager
* The string translation manager.
*/
public static function translation() {
return static::$container->get('string_translation');
}
}
......@@ -16,6 +16,7 @@
use Drupal\Core\DependencyInjection\Compiler\RegisterRouteEnhancersPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterParamConvertersPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterServicesForDestructionPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterStringTranslatorsPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;
......@@ -60,6 +61,8 @@ public function build(ContainerBuilder $container) {
// Add the compiler pass that will process the tagged services.
$container->addCompilerPass(new RegisterPathProcessorsPass());
$container->addCompilerPass(new ListCacheBinsPass());
// Add the compiler pass for appending string translators.
$container->addCompilerPass(new RegisterStringTranslatorsPass());
}
/**
......
<?php
/**
* @file
* Contains Drupal\Core\DependencyInjection\Compiler\RegisterStringTranslatorsPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds services tagged 'string_translator' to the string_translation service.
*/
class RegisterStringTranslatorsPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('string_translation')) {
return;
}
$access_manager = $container->getDefinition('string_translation');
foreach ($container->findTaggedServiceIds('string_translator') as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$access_manager->addMethodCall('addTranslator', array(new Reference($id), $priority));
}
}
}
......@@ -7,7 +7,9 @@
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageManager;
use Drupal\Core\StringTranslation\Translator\TranslatorInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
......@@ -25,15 +27,25 @@ class LanguageRequestSubscriber implements EventSubscriberInterface {
*/
protected $languageManager;
/**
* The translation service.
*
* @var \Drupal\Core\Translation\Translator\TranslatorInterface
*/
protected $translation;
/**
* Constructs a LanguageRequestSubscriber object.
*
* @param \Drupal\Core\Language\LanguageManager $language_manager
* The language manager service.
*
* @param \Drupal\Core\Translation\Translator\TranslatorInterface $translation
* The translation service.
*/
public function __construct(LanguageManager $language_manager) {
public function __construct(LanguageManager $language_manager, TranslatorInterface $translation) {
$this->languageManager = $language_manager;
$this->translation = $translation;
}
/**
......@@ -45,6 +57,10 @@ public function __construct(LanguageManager $language_manager) {
public function onKernelRequestLanguage(GetResponseEvent $event) {
if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
$this->languageManager->setRequest($event->getRequest());
// After the language manager has initialized, set the default langcode
// for the string translations.
$langcode = $this->languageManager->getLanguage(Language::TYPE_INTERFACE)->langcode;
$this->translation->setDefaultLangcode($langcode);
}
}
......
<?php
/**
* @file
* Contains \Drupal\Core\StringTranslation\TranslationManager.
*/
namespace Drupal\Core\StringTranslation;
use Drupal\Core\StringTranslation\Translator\TranslatorInterface;
use Drupal\Component\Utility\String;
/**
* Defines a chained translation implementation combining multiple translators.
*/
class TranslationManager implements TranslatorInterface {
/**
* An array of active translators keyed by priority.
*
* @var array
* Array of \Drupal\Core\Translation\Translator\TranslatorInterface objects
*/
protected $translators = array();
/**
* Holds the array of translators sorted by priority.
*
* If this is NULL a rebuild will be triggered.
*
* @var array
* An array of path processor objects.
*
* @see \Drupal\Core\StringTranslation\TranslationManager::addTranslator()
* @see \Drupal\Core\StringTranslation\TranslationManager::sortTranslators()
*/
protected $sortedTranslators = NULL;
/**
* The default langcode used in translations.
*
* @var string
* A language code.
*/
protected $defaultLangcode;
/**
* Constructs a TranslationManager object.
*/
public function __construct() {
// @todo Inject language_manager or config system after language_default
// variable is converted to CMI.
$this->defaultLangcode = language_default()->langcode;
}
/**
* Appends a translation system to the translation chain.
*
* @param \Drupal\Core\Translation\Translator\TranslatorInterface $translator
* The translation interface to be appended to the translation chain.
* @param int $priority
* The priority of the logger being added.
*
* @return \Drupal\Core\Translation\TranslationManager
* The called object.
*/
public function addTranslator(TranslatorInterface $translator, $priority = 0) {
$this->translators[$priority][] = $translator;
// Reset sorted translators property to trigger rebuild.
$this->sortedTranslators = NULL;
return $this;
}
/**
* Sorts translators according to priority.
*
* @return array
* A sorted array of translators objects.
*/
protected function sortTranslators() {
$sorted = array();
krsort($this->translators);
foreach ($this->translators as $translators) {
$sorted = array_merge($sorted, $translators);
}
return $sorted;
}
/**
* {@inheritdoc}
*/
public function getStringTranslation($langcode, $string, $context) {
if ($this->sortedTranslators === NULL) {
$this->sortedTranslators = $this->sortTranslators();
}
foreach ($this->sortedTranslators as $translator) {
$translation = $translator->getStringTranslation($langcode, $string, $context);
if ($translation !== FALSE) {
return $translation;
}
}
// No translator got a translation.
return FALSE;
}
/**
* Translates a string to the current language or to a given language.
*
* @param string $string
* A string containing the English string to translate.
* @param array $args
* An associative array of replacements to make after translation. Based
* on the first character of the key, the value is escaped and/or themed.
* See \Drupal\Core\Utility\String::format() for details.
* @param array $options
* An associative array of additional options, with the following elements:
* - 'langcode': The language code to translate to a language other than
* what is used to display the page.
* - 'context': The context the source string belongs to.
*
* @return string
* The translated string.
*
* @see t()
* @see \Drupal\Core\Utility\String::format()
*/
public function translate($string, array $args = array(), array $options = array()) {
// Merge in defaults.
if (empty($options['langcode'])) {
$options['langcode'] = $this->defaultLangcode;
}
if (empty($options['context'])) {
$options['context'] = '';
}
$translation = $this->getStringTranslation($options['langcode'], $string, $options['context']);
$string = $translation === FALSE ? $string : $translation;
if (empty($args)) {
return $string;
}
else {
return String::format($string, $args);
}
}
/**
* Sets the default langcode.
*
* @param string $langcode
* A language code.
*/
public function setDefaultLangcode($langcode) {
$this->defaultLangcode = $langcode;
}
/**
* {@inheritdoc}
*/
public function reset() {
if ($this->sortedTranslators === NULL) {
$this->sortedTranslators = $this->sortTranslators();
}
foreach ($this->sortedTranslators as $translator) {
$translator->reset();
}
}
}
<?php
/**
* @file
* Contains \Drupal\Core\StringTranslation\Translator\CustomStrings.
*/
namespace Drupal\Core\StringTranslation\Translator;
/**
* String translator using overrides from variables.
*
* This is a high performance way to provide a handful of string replacements.
* See settings.php for examples.
*/
class CustomStrings extends StaticTranslation {
/**
* {@inheritdoc}
*/
protected function loadLanguage($langcode) {
return variable_get('locale_custom_strings_' . $langcode, array());
}
}
<?php
/**
* @file
* Contains \Drupal\Core\StringTranslation\Translator\FileTranslation.
*/
namespace Drupal\Core\StringTranslation\Translator;
use Drupal\Component\Gettext\PoStreamReader;
use Drupal\Component\Gettext\PoMemoryWriter;
/**
* File based string translation.
*
* Translates a string when some systems are not available.
*
* Used during the install process, when database, theme, and localization
* system is possibly not yet available.
*
* Use t() if your code will never run during the Drupal installation phase.
* Use st() if your code will only run during installation.
*/
class FileTranslation extends StaticTranslation {
/**
* Directory to find translation files in the file system.
*
* @var string
*/
protected $directory;
/**
* Constructs a StaticTranslation object.
*
* @param string $directory
* The directory to retrieve file translations from.
*/
public function __construct($directory) {
parent::__construct();
$this->directory = $directory;
}
/**
* {@inheritdoc}
*/
protected function loadLanguage($langcode) {
// If the given langcode was selected, there should be at least one .po
// file with its name in the pattern drupal-$version.$langcode.po.
// This might or might not be the entire filename. It is also possible
// that multiple files end with the same suffix, even if unlikely.
$files = $this->findTranslationFiles($langcode);
if (!empty($files)) {
return $this->filesToArray($langcode, $files);
}
else {
return array();
}
}
/**
* Finds installer translations either for a specific or all languages.
*
* @param string $langcode
* (optional) The language code corresponding to the language for which we
* want to find translation files. If omitted, information on all available
* files will be returned.
*
* @return array
* An associative array of file information objects keyed by file URIs as
* returned by file_scan_directory().
*
* @see file_scan_directory()
*/
public function findTranslationFiles($langcode = NULL) {
$files = file_scan_directory($this->directory, '!drupal-\d+\.\d+\.' . (!empty($langcode) ? preg_quote($langcode, '!') : '[^\.]+') . '\.po$!', array('recurse' => FALSE));
return $files;
}
/**
* Reads the given Gettext PO files into a data structure.
*
* @param string $langcode
* Language code string.
* @param array $files
* List of file objects with URI properties pointing to read.
*
* @return array
* Structured array as produced by a PoMemoryWriter.
*
* @see \Drupal\Component\Gettext\PoMemoryWriter