Commit 662c42a4 authored by webchick's avatar webchick

Issue #1785086 by Jose Reyero, Gábor Hojtsy, catch, loganfsmyth, nedjo,...

Issue #1785086 by Jose Reyero, Gábor Hojtsy, catch, loganfsmyth, nedjo, stella: Added Introduce a generic API for interface translation strings.
parent e7787c9c
......@@ -8,6 +8,8 @@
namespace Drupal\locale;
use Drupal\Core\Utility\CacheArray;
use Drupal\locale\SourceString;
use Drupal\locale\TranslationString;
/**
* Extends CacheArray to allow for dynamic building of the locale cache.
......@@ -26,12 +28,20 @@ class LocaleLookup extends CacheArray {
*/
protected $context;
/**
* The locale storage
*
* @var Drupal\locale\StringStorageInterface
*/
protected $stringStorage;
/**
* Constructs a LocaleCache object.
*/
public function __construct($langcode, $context) {
public function __construct($langcode, $context, $stringStorage) {
$this->langcode = $langcode;
$this->context = (string) $context;
$this->stringStorage = $stringStorage;
// Add the current user's role IDs to the cache key, this ensures that, for
// example, strings for admin menu items and settings forms are not cached
......@@ -44,36 +54,30 @@ public function __construct($langcode, $context) {
* Overrides DrupalCacheArray::resolveCacheMiss().
*/
protected function resolveCacheMiss($offset) {
$translation = db_query("SELECT s.lid, t.translation, s.version FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.source = :source AND s.context = :context", array(
':language' => $this->langcode,
':source' => $offset,
':context' => $this->context,
))->fetchObject();
$translation = $this->stringStorage->findTranslation(array(
// These are the search conditions.
'language' => $this->langcode,
'source' => $offset,
'context' => $this->context
), array(
// Search options. We just need this limited set of fields.
'fields' => array('lid', 'version', 'translation'),
));
if ($translation) {
if ($translation->version != VERSION) {
// This is the first use of this string under current Drupal version.
// Update the {locales_source} table to indicate the string is current.
db_update('locales_source')
->fields(array('version' => VERSION))
->condition('lid', $translation->lid)
->execute();
}
$this->stringStorage->checkVersion($translation, VERSION);
$value = !empty($translation->translation) ? $translation->translation : TRUE;
}
else {
// We don't have the source string, update the {locales_source} table to
// indicate the string is not translated.
db_merge('locales_source')
->insertFields(array(
'location' => request_uri(),
'version' => VERSION,
))
->key(array(
'source' => $offset,
'context' => $this->context,
))
->execute();
$value = TRUE;
$this->stringStorage->createString(array(
'source' => $offset,
'context' => $this->context,
'location' => request_uri(),
'version' => VERSION
))->save();
$value = TRUE;
}
$this->storage[$offset] = $value;
// Disabling the usage of string caching allows a module to watch for
......
......@@ -10,6 +10,8 @@
use Drupal\Component\Gettext\PoHeader;
use Drupal\Component\Gettext\PoItem;
use Drupal\Component\Gettext\PoReaderInterface;
use Drupal\locale\TranslationString;
use PDO;
/**
* Gettext PO reader working with the locale module database.
......@@ -106,71 +108,67 @@ function setHeader(PoHeader $header) {
/**
* Builds and executes a database query based on options set earlier.
*/
private function buildQuery() {
private function loadStrings() {
$langcode = $this->_langcode;
$options = $this->_options;
$conditions = array();
if (array_sum($options) == 0) {
// If user asked to not include anything in the translation files,
// that would not make sense, so just fall back on providing a template.
$langcode = NULL;
// Force option to get both translated and untranslated strings.
$options['not_translated'] = TRUE;
}
// Build and execute query to collect source strings and translations.
$query = db_select('locales_source', 's');
if (!empty($langcode)) {
if ($options['not_translated']) {
// Left join to keep untranslated strings in.
$query->leftJoin('locales_target', 't', 's.lid = t.lid AND t.language = :language', array(':language' => $langcode));
}
else {
// Inner join to filter for only translations.
$query->innerJoin('locales_target', 't', 's.lid = t.lid AND t.language = :language', array(':language' => $langcode));
}
$conditions['language'] = $langcode;
// Translate some options into field conditions.
if ($options['customized']) {
if (!$options['not_customized']) {
// Filter for customized strings only.
$query->condition('t.customized', LOCALE_CUSTOMIZED);
$conditions['customized'] = LOCALE_CUSTOMIZED;
}
// Else no filtering needed in this case.
}
else {
if ($options['not_customized']) {
// Filter for non-customized strings only.
$query->condition('t.customized', LOCALE_NOT_CUSTOMIZED);
$conditions['customized'] = LOCALE_NOT_CUSTOMIZED;
}
else {
// Filter for strings without translation.
$query->isNull('t.translation');
$conditions['translated'] = FALSE;
}
}
$query->fields('t', array('translation'));
if (!$options['not_translated']) {
// Filter for string with translation.
$conditions['translated'] = TRUE;
}
return locale_storage()->getTranslations($conditions);
}
else {
$query->leftJoin('locales_target', 't', 's.lid = t.lid');
// If no language, we don't need any of the target fields.
return locale_storage()->getStrings($conditions);
}
$query->fields('s', array('lid', 'source', 'context', 'location'));
$this->_result = $query->execute();
}
/**
* Get the database result resource for the given language and options.
*/
private function getResult() {
private function readString() {
if (!isset($this->_result)) {
$this->buildQuery();
$this->_result = $this->loadStrings();
}
return $this->_result;
return array_shift($this->_result);
}
/**
* Implements Drupal\Component\Gettext\PoReaderInterface::readItem().
*/
function readItem() {
$result = $this->getResult();
$values = $result->fetchAssoc();
if ($values) {
if ($string = $this->readString()) {
$values = (array)$string;
$poItem = new PoItem();
$poItem->setFromArray($values);
return $poItem;
......
......@@ -11,6 +11,8 @@
use Drupal\Component\Gettext\PoItem;
use Drupal\Component\Gettext\PoReaderInterface;
use Drupal\Component\Gettext\PoWriterInterface;
use Drupal\locale\SourceString;
use Drupal\locale\TranslationString;
/**
* Gettext PO writer working with the locale module database.
......@@ -226,12 +228,11 @@ private function importString(PoItem $item) {
// Look up the source string and any existing translation.
$string = db_query("SELECT s.lid, t.customized FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.source = :source AND s.context = :context", array(
':source' => $source,
':context' => $context,
':language' => $this->_langcode,
))
->fetchObject();
$string = locale_storage()->findTranslation(array(
'language' => $this->_langcode,
'source' => $source,
'context' => $context
));
if (!empty($translation)) {
// Skip this string unless it passes a check for dangerous code.
......@@ -240,64 +241,43 @@ private function importString(PoItem $item) {
$this->_report['skips']++;
return 0;
}
elseif (isset($string->lid)) {
if (!isset($string->customized)) {
elseif ($string) {
$string->setString($translation);
if ($string->isNew()) {
// No translation in this language.
db_insert('locales_target')
->fields(array(
'lid' => $string->lid,
'language' => $this->_langcode,
'translation' => $translation,
'customized' => $customized,
))
->execute();
$string->setValues(array(
'language' => $this->_langcode,
'customized' => $customized
));
$string->save();
$this->_report['additions']++;
}
elseif ($overwrite_options[$string->customized ? 'customized' : 'not_customized']) {
// Translation exists, only overwrite if instructed.
db_update('locales_target')
->fields(array(
'translation' => $translation,
'customized' => $customized,
))
->condition('language', $this->_langcode)
->condition('lid', $string->lid)
->execute();
$string->customized = $customized;
$string->save();
$this->_report['updates']++;
}
return $string->lid;
}
else {
// No such source string in the database yet.
$lid = db_insert('locales_source')
->fields(array(
'source' => $source,
'context' => $context,
))
->execute();
db_insert('locales_target')
->fields(array(
'lid' => $lid,
'language' => $this->_langcode,
'translation' => $translation,
'customized' => $customized,
))
->execute();
$string = locale_storage()->createString(array('source' => $source, 'context' => $context))
->save();
$target = locale_storage()->createTranslation(array(
'lid' => $string->getId(),
'language' => $this->_langcode,
'translation' => $translation,
'customized' => $customized,
))->save();
$this->_report['additions']++;
return $lid;
return $string->lid;
}
}
elseif (isset($string->lid) && isset($string->customized) && $overwrite_options[$string->customized ? 'customized' : 'not_customized']) {
elseif ($string && !$string->isNew() && $overwrite_options[$string->customized ? 'customized' : 'not_customized']) {
// Empty translation, remove existing if instructed.
db_delete('locales_target')
->condition('language', $this->_langcode)
->condition('lid', $string->lid)
->execute();
$string->delete();
$this->_report['deletes']++;
return $string->lid;
}
......
<?php
/**
* @file
* Definition of Drupal\locale\SourceString.
*/
namespace Drupal\locale;
use Drupal\locale\LocaleString;
use PDO;
/**
* Defines the locale source string object.
*
* This class represents a module-defined string value that is to be translated.
* This string must at least contain a 'source' field, which is the raw source
* value, and is assumed to be in English language.
*/
class SourceString extends StringBase {
/**
* Implements Drupal\locale\StringInterface::isSource().
*/
public function isSource() {
return isset($this->source);
}
/**
* Implements Drupal\locale\StringInterface::isTranslation().
*/
public function isTranslation() {
return FALSE;
}
/**
* Implements Drupal\locale\LocaleString::getString().
*/
public function getString() {
return isset($this->source) ? $this->source : '';
}
/**
* Implements Drupal\locale\LocaleString::setString().
*/
public function setString($string) {
$this->source = $string;
return $this;
}
/**
* Implements Drupal\locale\LocaleString::isNew().
*/
public function isNew() {
return empty($this->lid);
}
}
<?php
/**
* @file
* Definition of Drupal\locale\StringBase.
*/
namespace Drupal\locale;
/**
* Defines the locale string base class.
*/
abstract class StringBase implements StringInterface {
/**
* The string identifier.
*
* @var integer
*/
public $lid;
/**
* The string location.
*
* @var string
*/
public $location;
/**
* The source string.
*
* @var string
*/
public $source;
/**
* The string context.
*
* @var string
*/
public $context;
/**
* The string version.
*
* @var string
*/
public $version;
/**
* The locale storage this string comes from or is to be saved to.
*
* @var Drupal\locale\StringStorageInterface
*/
protected $storage;
/**
* Constructs a new locale string object.
*
* @param object|array $values
* Object or array with initial values.
*/
public function __construct($values = array()) {
$this->setValues((array)$values);
}
/**
* Implements Drupal\locale\StringInterface::getId().
*/
public function getId() {
return isset($this->lid) ? $this->lid : NULL;
}
/**
* Implements Drupal\locale\StringInterface::setId().
*/
public function setId($lid) {
$this->lid = $lid;
return $this;
}
/**
* Implements Drupal\locale\StringInterface::getVersion().
*/
public function getVersion() {
return isset($this->version) ? $this->version : NULL;
}
/**
* Implements Drupal\locale\StringInterface::setVersion().
*/
public function setVersion($version) {
$this->version = $version;
return $this;
}
/**
* Implements Drupal\locale\StringInterface::getPlurals().
*/
public function getPlurals() {
return explode(LOCALE_PLURAL_DELIMITER, $this->getString());
}
/**
* Implements Drupal\locale\StringInterface::setPlurals().
*/
public function setPlurals($plurals) {
$this->setString(implode(LOCALE_PLURAL_DELIMITER, $plurals));
return $this;
}
/**
* Implements Drupal\locale\StringInterface::setStorage().
*/
public function setStorage($storage) {
$this->storage = $storage;
return $this;
}
/**
* Implements Drupal\locale\StringInterface::setValues().
*/
public function setValues(array $values, $override = TRUE) {
foreach ($values as $key => $value) {
if (property_exists($this, $key) && ($override || !isset($this->$key))) {
$this->$key = $value;
}
}
return $this;
}
/**
* Implements Drupal\locale\StringInterface::getValues().
*/
public function getValues(array $fields) {
$values = array();
foreach ($fields as $field) {
if (isset($this->$field)) {
$values[$field] = $this->$field;
}
}
return $values;
}
/**
* Implements Drupal\locale\LocaleString::save().
*/
public function save() {
$this->getStorage()->save($this);
return $this;
}
/**
* Implements Drupal\locale\LocaleString::delete().
*/
public function delete() {
if (!$this->isNew()) {
$this->getStorage()->delete($this);
}
return $this;
}
/**
* Gets the storage to which this string is bound.
*
* @throws Drupal\locale\StringStorageException
* In case the string doesn't have an storage set, an exception is thrown.
*/
protected function getStorage() {
if (isset($this->storage)) {
return $this->storage;
}
else {
throw new StringStorageException(format_string('The string cannot be saved nor deleted because its not bound to an storage: @string', array(
'@string' => $string->getString()
)));
}
}
}
<?php
/**
* @file
* Definition of Drupal\locale\StringInterface.
*/
namespace Drupal\locale;
/**
* Defines the locale string interface.
*/
interface StringInterface {
/**
* Gets the string unique identifier.
*
* @return int
* The string identifier.
*/
public function getId();
/**
* Sets the string unique identifier.
*
* @param int $id
* The string identifier.
*
* @return Drupal\locale\LocaleString
* The called object.
*/
public function setId($id);
/**
* Gets the string version.
*
* @return string
* Version identifier.
*/
public function getVersion();
/**
* Sets the string version.
*
* @param string $version
* Version identifier.
*
* @return Drupal\locale\LocaleString
* The called object.
*/
public function setVersion($version);
/**
* Gets plain string contained in this object.
*
* @return string
* The string contained in this object.
*/
public function getString();
/**
* Sets the string contained in this object.
*
* @param string $string
* String to set as value.
*
* @return Drupal\locale\LocaleString
* The called object.
*/
public function setString($string);
/**
* Splits string to work with plural values.
*
* @return array
* Array of strings that are plural variants.
*/
public function getPlurals();
/**
* Sets this string using array of plural values.
*
* Serializes plural variants in one string glued by LOCALE_PLURAL_DELIMITER.
*
* @param array $plurals
* Array of strings with plural variants.
*
* @return Drupal\locale\LocaleString
* The called object.
*/
public function setPlurals($plurals);
/**
* Sets the string storage.
*
* @param Drupal\locale\StringStorageInterface $storage
* The storage to use for this string.
*
* @return Drupal\locale\LocaleString
* The called object.
*/
public function setStorage($storage);
/**
* Checks whether the object is not saved to storage yet.
*
* @return bool
* TRUE if the object exists in the storage, FALSE otherwise.
*/
public function isNew();
/**
* Checks whether the object is a source string.
*
* @return bool
* TRUE if the object is a source string, FALSE otherwise.
*/
public function isSource();
/**
* Checks whether the object is a translation string.
*
* @return bool
* TRUE if the object is a translation string, FALSE otherwise.
*/
public function isTranslation();
/**
* Sets an array of values as object properties.
*
* @param array $values
* Array with values indexed by property name,
* @param bool $override
* (optional) Whether to override already set fields, defaults to TRUE.
*
* @return Drupal\locale\LocaleString
* The called object.
*/
public function setValues(array $values, $override = TRUE);
/**
* Gets field values that are set for given field names.
*
* @param array $fields
* Array of field names.
*
* @return array
* Array of field values indexed by field name.
*/
public function getValues(array $fields);
/**
* Saves string object to storage.
*
* @return Drupal\locale\LocaleString
* The called object.
*
* @throws Drupal\locale\StringStorageException
* In case of failures, an exception is thrown.
*/
public function save();
/**
* Deletes string object from storage.
*
* @return Drupal\locale\LocaleString
* The called object.
*
* @throws Drupal\locale\StringStorageException
* In case of failures, an exception is thrown.
*/
public function delete();
}