mustache_templates-8.x-1.0-beta4/src/Plugin/mustache/Magic/Translation.php
src/Plugin/mustache/Magic/Translation.php
<?php
namespace Drupal\mustache\Plugin\mustache\Magic;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Access\AccessibleInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\mustache\Element\Mustache;
use Drupal\mustache\Helpers\MustacheRenderTemplate;
use Drupal\mustache\Plugin\MustacheMagic;
use Drupal\mustache\Render\Markup;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Pass through template contents to Drupal's string translation system.
*
* Usage:
*
* You can use the "magical" {{#t.<langcode>}} section variable to pass template
* contents to Drupal's translation system. You can specify an ISO 639-1
* language code as target translation or use either one of "i", "interface" and
* "current" to use the current interface language, for example {{#t.current}}.
*
* @code
* {{#t.i}}I am an english text and will be substituted by an according translation of the current interface language.{{/t.i}}
* {{#t.de}}I am an english text and will be substituted by an according German translation.{{/t.de}}
* @endcode
*
* @MustacheMagic(
* id = "t",
* label = @Translation("Translation"),
* description = @Translation("Use <b>{{#t.<langcode>}}</b> to pass template contents to Drupal's translation system. You can specify an ISO 639-1 language code as target translation or use either "i", "interface" or "current" to use the current language. Examples: <ul><li>{{#t.i}}I am an english text and will be substituted by an according translation of the current interface language.{{/t.i}}</li><li>{{#t.de}}I am an english text and will be substituted by an according German translation.{{/t.de}}</li></ul>")
* )
*/
class Translation extends MustacheMagic {
use StringTranslationTrait;
/**
* Whether this object is a clone.
*
* @var bool
*/
protected $cloned = FALSE;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The langcode to use.
*
* @var string|null
*/
protected $langcode;
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* The client library for magic translations.
*
* @var string
*/
public static $library = 'mustache/magic.translation';
/**
* A list of reserved keywords that may be used as synonym for UI language.
*
* @var array
*/
public static $interfaceLanguageKeywords = ['i', 'interface', 'current'];
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$instance->languageManager = $container->get('language_manager');
$instance->stringTranslation = $container->get('string_translation');
$instance->renderer = $container->get('renderer');
return $instance;
}
/**
* Implementation of the magic __isset() method.
*/
public function __isset($name): bool {
return !empty($name) && (strlen($name) === 2 || in_array(mb_strtolower(trim($name)), static::$interfaceLanguageKeywords));
}
/**
* Implementation of the magic __get() method.
*/
public function __get($name) {
if (!$this->cloned) {
$cloned = clone $this;
$cloned->cloned = TRUE;
return $cloned->__get($name);
}
$name = mb_strtolower(trim($name));
$this->langcode = $name;
return $this;
}
/**
* Implements the magic __invoke() method, used as higher-order section.
*/
public function __invoke($template_content = NULL, $render = NULL) {
if (!isset($template_content, $render)) {
return;
}
// Attach the library at the beginning, so that BubbleableMetadata will
// include that one (and will not forget about it).
$this->attachLibrary();
if (!empty($this->langcode) && !in_array($this->langcode, static::$interfaceLanguageKeywords) && !$this->languageManager->getLanguage($this->langcode)) {
// Invalid or nonexistent langcode specified.
$this->langcode = NULL;
}
$current_langcode = $this->languageManager->getCurrentLanguage()->getId();
$langcode = $this->langcode ?? $current_langcode;
if (in_array($langcode, static::$interfaceLanguageKeywords)) {
$langcode = $current_langcode;
}
$language = $this->languageManager->getLanguage($langcode);
if (!$language) {
// Nothing to do when language does not exist for the given langcode.
return $render($template_content);
}
if (isset($this->langcode) && !empty($this->element['#use_sync'])) {
$this->element['#attached']['drupalSettings']['mustache']['lang'][$this->langcode] = $langcode;
}
$requires_isolated_render = FALSE;
$translated_content = (string) $this->t($template_content, [], [
'langcode' => $langcode,
]);
$template_name = $langcode . '-' . hash('md4', $translated_content);
$bubbleable_metadata = BubbleableMetadata::createFromRenderArray($this->element);
if (!$this->isInline() && \Drupal::hasService('mustache.template_storage')) {
/** @var \Drupal\mustache_magic\Storage\MustacheTemplateStorage $template_storage */
$template_storage = \Drupal::service('mustache.template_storage');
$template_storage->registerTemplate($template_name, $translated_content);
$build = MustacheRenderTemplate::build($template_name);
}
else {
$build = MustacheRenderTemplate::build($template_name, $translated_content);
}
$build_render_array = &$build->toRenderArray();
$data_keys = [
['#data'],
['#override_data'],
['#with_tokens', 'data'],
];
if (!empty($this->element['#sync']['items'])) {
foreach ($this->element['#sync']['items'] as $sync_i => &$sync_item) {
if (isset($sync_item['data'])) {
$data_keys[] = ['#sync', 'items', $sync_i, 'data'];
}
if (isset($sync_item['url'])) {
$data_keys[] = ['#sync', 'items', $sync_i, 'url'];
}
}
}
foreach ($data_keys as $data_key) {
$key_exists = FALSE;
$data = NestedArray::getValue($this->element, $data_key, $key_exists);
if ($key_exists) {
if ($data_key[0] === '#sync' && $data_key[1] === 'items' && is_string($data)) {
$data = json_decode($data, TRUE);
}
if (is_array($data)) {
foreach ($data as $key => $value) {
if ($value instanceof TranslatableInterface) {
if ($value->language()->getId() !== $langcode && $value->hasTranslation($langcode)) {
$value = $value->getTranslation($langcode);
if ($value instanceof AccessibleInterface && !$value->access('view')) {
continue;
}
if ($value) {
$requires_isolated_render = TRUE;
$data[$key] = $value;
}
}
}
elseif ($value instanceof TranslatableMarkup) {
$options = $value->getOptions();
if ((!isset($options['langcode']) && ($current_langcode !== $langcode)) || (isset($options['langcode']) && $options['langcode'] !== $langcode)) {
$requires_isolated_render = TRUE;
$options['langcode'] = $langcode;
}
$data[$key] = $this->t($value->getUntranslatedString(), $value->getArguments(), $options);
}
elseif (is_string($value)) {
$data[$key] = (string) $this->t($value, [], [
'langcode' => $langcode,
]);
if ($value !== $data[$key]) {
$requires_isolated_render = TRUE;
}
}
}
NestedArray::setValue($build_render_array, $data_key, $data, TRUE);
}
elseif ($url = Mustache::getUrlFromParam($data)) {
$url_string = $url->toString();
$query_params = $url->getOption('query');
$url_lang = $url->getOption('language');
$path_lang = NULL;
if ($url_path = parse_url($url_string, PHP_URL_PATH)) {
if (substr($url_path, 0, 1) == '/') {
$url_path = substr($url_path, 1);
}
$url_path_parts = explode('/', $url_path);
$path_arg = (string) array_shift($url_path_parts);
$path_lang = !empty($path_arg) ? $this->languageManager->getLanguage($path_arg) : NULL;
}
$lang_set = $url_lang || !empty($query_params['langcode']) || $path_lang;
$lang_equal_or_empty = !$lang_set || ($url_lang && $url_lang->getId() == $langcode) || ($path_lang && $path_lang->getId() == $langcode) || (isset($query_params['langcode']) && $query_params['langcode'] == $langcode);
if ($lang_equal_or_empty && (!$url->isExternal() || $url->isRouted())) {
$url->setOption('language', $language);
}
if ($path_lang) {
array_unshift($url_path_parts, $langcode);
$url_string = str_replace($url_path, implode('/', $url_path_parts), $url_string);
$url = Mustache::getUrlFromParam($url_string);
}
if (!$lang_equal_or_empty) {
$requires_isolated_render = TRUE;
$url = clone $url;
}
if (!$url->isExternal() || $url->isRouted()) {
$url->setOption('language', $language);
$lang_set = TRUE;
}
if (!$lang_set || isset($query_params['langcode'])) {
$url = Mustache::rebuildUrl($url, ['langcode' => $langcode]);
}
NestedArray::setValue($build_render_array, $data_key, $url, TRUE);
if ($data_key[0] === '#sync') {
if ($lang_equal_or_empty && !$requires_isolated_render) {
$url->setAbsolute($url->isExternal());
NestedArray::setValue($this->element, $data_key, json_encode($url->toString(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_FORCE_OBJECT), TRUE);
unset($build_render_array['#sync']['items'][$data_key[2]]);
}
else {
$wrapper_selector = NULL;
if (!empty($this->element['#sync']['wrapper_tag']) && isset($this->element['#sync']['attributes']['id'])) {
$wrapper_selector = '#' . $this->element['#sync']['attributes']['id'];
}
elseif (!empty($this->element['#sync']['into'])) {
$wrapper_selector = $this->element['#sync']['into'];
}
if ($wrapper_selector && $wrapper_selector !== '#') {
$build_render_array['#sync']['items'][$data_key[2]]['trigger'] = [
[$wrapper_selector, 'mustacheSyncBegin', -1],
];
}
else {
foreach (['delay', 'period', 'limit', 'trigger'] as $sync_setting) {
if (isset($this->element['#sync']['items'][$data_key[2]][$sync_setting])) {
$build_render_array['#sync']['items'][$data_key[2]][$sync_setting] = $this->element['#sync']['items'][$data_key[2]][$sync_setting];
}
}
}
}
}
}
}
}
if (empty($build_render_array['#sync']['items'])) {
unset($build_render_array['#sync']);
}
if ($this->withTokens() && ((!isset($this->element['#with_tokens']['options']['langcode']) && ($current_langcode !== $langcode)) || (isset($this->element['#with_tokens']['options']['langcode']) && $this->element['#with_tokens']['options']['langcode'] !== $langcode))) {
$requires_isolated_render = TRUE;
$build_render_array['#with_tokens']['options']['langcode'] = $langcode;
}
if ($requires_isolated_render) {
$build_render_array['#cache']['contexts'][] = 'languages';
if (!empty($build_render_array['#sync'])) {
$build_render_array['#sync']['wrapper_tag'] = NULL;
unset($build_render_array['#sync']['into']);
// Provide a mapping from untranslated to translated template contents.
// The client library of this plugin then takes care to do the math.
$trans_key = "trans-${current_langcode}-${template_name}";
if (isset($template_storage)) {
$template_storage->registerTemplate($trans_key, $template_content);
$this->element['#partials'][$trans_key] = $trans_key;
}
else {
$this->element['#inline_partials'][$trans_key] = [
'#type' => 'mustache_template_js_inline',
'#template' => $trans_key,
'#inline' => $template_content,
];
}
// Prevent double-includes of partials, as this is a nested invokation.
$build_render_array['#skip_partials'] = TRUE;
}
$renderer = $this->renderer;
$rendered = $renderer->executeInRenderContext(new RenderContext(), function () use ($renderer, &$build_render_array) {
return $renderer->render($build_render_array);
});
$bubbleable_metadata
->addCacheableDependency(BubbleableMetadata::createFromRenderArray($build_render_array))
->applyTo($this->element);
// We need to replace the opening curly brackets, so that Mustache.php's
// lambda helper cannot chime in and would try to replace Mustache
// variables within the inline template with current context values.
return Markup::create(str_replace('{{', '\{\{', (string) $rendered));
}
else {
$bubbleable_metadata->addCacheContexts(['languages'])->applyTo($this->element);
return $render($translated_content);
}
}
}
