Problem/Motivation

We've long wanted this functionality.
I brought it up July 21 2020

wondering if it would be possible to allow the tokens to process a language suffix in the token to get the specific language value otherwise grab the current interface language

@berdir had this to say July 22 2020:

The language is passed in as an option, token syntax simply isn't flexible enough to allow arguments like that. Each token has one type and has to pass things to its children. It would also explode the number of tokens which is already a massive concern.

In response to @berdir, obviously for us wanting this functionality, it should only be enabled when there are multiple languages available so I don't see the argument for concern about the number of tokens. It could be also an optional functionality which could be disabled by default but enableable.

The missing tokens are:

(examples):

  • [term:field_company_code:fr] and [term:field_company_code:nl]
  • [node:field_name:fr] and [node:field_name:nl]

OR, if it's easier to do it this way:

(examples):

  • [node:fr:field_company_code] and [node:nl:field_company_code]
  • [node:fr:field_name] and [node:nl:field_name]

OR maybe we hack something in like this:

(examples):

  • [node_fr:field_company_name] and [node_nl:field_company_name]
  • we could check the token for the string , if it's node_xx , string parse it and set the langcode to xx.

OR hack something like this

  • OR [node:field_company_name_fr] and [node_nl:field_company_name_fr]
  • we could check the token for the string , if it's field_company_name_xx , string parse it and set the langcode to xx.

We need to have standard tokens for things like nodes which are for a specific language. For things like multi-language emails it is not sufficient to simply use the current context language to determine the language of the token value.

So for a token like this: [node:field_shared_node:entity:title], we need something like [node:field_shared_node:entity:title:fr]

not sure the best format for this, just adding langcode to the end seems like it would be difficult to parse as not being for something else. Looking at date tokens as an example they do not seem to worry about this as these are all valid:
date:custom:?
date:format_1
date:format_2

Maybe something like: [node:field_shared_node:entity:title:language:fr] would be preferred?

I have seen a few other language specific token issues and this exact issue reported in one of them but the reply was that it was out of scope of that particular issue - so guessing this hasn't yet been requested in its own issue. Apologies if it has been.

Also, not sure if this would be part of this module or perhaps its own contrib module?

Steps to reproduce

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

Issue fork token-3276537

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

liquidcms created an issue. See original summary.

liquidcms’s picture

Issue summary: View changes
joseph.olstad’s picture

You can implement your own tokens in your own my_module module. Sorta like this:


use Drupal\node\Entity\Node;
use Drupal\Core\Render\Markup;
use Drupal\Core\Render\BubbleableMetadata;

/**
 * Implements hook_token_info().
 */
function my_module_token_info() {
  $info = [];
  $info['types']['my-module'] = [
    'name' => t('My Module Custom Token'),
    'description' => t('Custom Group to use in content moderation notification emails'),
  ];
  $info['tokens']['my-module']['translation-title'] = [
    'name' => t('Translation title'),
    'description' => t('A token to extract the translated node title'),
  ];
  $info['tokens']['my-module']['translation-url'] = [
    'name' => t('Translation url'),
    'description' => t('A token to extract the translated URL'),
  ];
  $info['tokens']['my-module']['title'] = [
    'name' => 'Title',
    'description' => t('A token to extract the current title for using submission form'),
  ];
  $info['tokens']['my-module']['url'] = [
    'name' => t('Url'),
    'description' => t('A token to extract the current URL for when using submission form'),
  ];
  $info['tokens']['my-module']['unpublished-url'] = [
    'name' => t('Unpublished url'),
    'description' => t('A token to extract the unpublished URL for anonymous users'),
  ];
  $info['tokens']['my-module']['translation-unpublished-url'] = [
    'name' => t('Translation unpublished url'),
    'description' => t('A token to extract the unpublished French URL for anonymous users'),
  ];
  $info['tokens']['my-module']['user-agent'] = [
    'name' => t('User agent'),
    'description' => t('A token to display the information of user agent'),
  ];
  $info['tokens']['my-module']['public-facing-url'] = [
    'name' => t('Public facing URL'),
    'description' => t('A token to extract the current public facing URL'),
  ];
  $info['tokens']['my-module']['translation-public-facing-url'] = [
    'name' => t('Translation Public facing URL'),
    'description' => t('A token to extract the current public facing URL in French'),
  ];
  return $info;
}

/**
 * Implements hook_tokens().
 */
function my_module_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
  $replacements = [];
  if ($type == 'my-module' && isset($data['entity']) && $data['entity'] instanceof NodeInterface) {
    $entity = $data['entity'];
    if ($entity->hasTranslation('fr')) {
      $translated_entity = $entity->getTranslation('fr');
    }
    else {
      $translated_entity = $entity;
    }
    $translated_title = $translated_entity->getTitle();
    $translated_url = $translated_entity->toLink()->toString();
    $current_title = $entity->getTitle();
    $current_url = $entity->toLink()->toString();
    $transLangID = $translated_entity->language()->getId();
    $currentLangID = \Drupal::languageManager()->getCurrentLanguage()->getId();
    $nodeID = $entity->id();

    $host = \Drupal::request()->getHost();
    $anonymous_url = str_replace("-admin", "", $host);
    $publicFacing_str = '<a href="https://' . $anonymous_url . '/' . $currentLangID . '/node/' . $nodeID . '">' . $current_title . '</a>';
    $publicFacing_url = Markup::create($publicFacing_str);

    $currentLang = \Drupal::languageManager()->getCurrentLanguage();
    $hashTag = MyModuleHelper::getHashTag($entity, $currentLang);
    $path = '<a href="' . $hashTag . '">' . $current_title . '</a>';
    $unpublished_url = Markup::create($path);

    $transLang = $translated_entity->language();
    $hashTagTrans = MyModuleHelper::getHashTag($translated_entity, $transLang);
    $transPath = '<a href="' . $hashTagTrans . '">' . $translated_title . '</a>';
    $translated_unpublished_url = Markup::create($transPath);

    $transPublicFacing_str = '<a href="https://' . $anonymous_url . '/' . $transLangID . '/node/' . $nodeID . '">' . $translated_title . '</a>';
    $translated_publicFacing_url = Markup::create($transPublicFacing_str);
    foreach ($tokens as $name => $original) {
      // Find the desired token by name.
      switch ($name) {
        case 'translation-title':
          $replacements[$original] = $translated_title;
          break;

        case 'translation-url':
          $replacements[$original] = $translated_url;
          break;

        case 'title':
          $replacements[$original] = $current_title;
          break;

        case 'url':
          $replacements[$original] = $current_url;
          break;

        case 'unpublished-url':
          $replacements[$original] = $unpublished_url;
          break;

        case 'translation-unpublished-url':
          $replacements[$original] = $translated_unpublished_url;
          break;

        case 'public-facing-url':
          $replacements[$original] = $publicFacing_url;
          break;

        case 'translation-public-facing-url':
          $replacements[$original] = $translated_publicFacing_url;
          break;
      }
    }
  }
  // Add token for webform only.
  if ($type == 'my-module' && !(isset($data['entity']) && $data['entity'] instanceof NodeInterface)) {
    foreach ($tokens as $name => $original) {
      switch ($name) {
        case 'user-agent':
          $useragent = $_SERVER['HTTP_USER_AGENT'];
          $replacements[$original] = $useragent;
          break;
      }
    }
  }
  return $replacements;
}

joseph.olstad’s picture

this link might help

liquidcms’s picture

Thanks Joesph, yes, i know how to make custom tokens (this would be my last resort).. was just looking for a more dynamic method than creating them every time a new field is added and more specifically, suggesting this should be part of the token module.

Looking at dynamic code now.

Not sure what the link you posted has to do with this; that is just about tokens for the language - not what this is about.

joseph.olstad’s picture

Hi @liquidcms, yes I'm also interested in what you're wanting to do, have done something similar but it's blurred right now, if I find something more relevant I'll share it. Please keep us updated on your progress.

Thanks!

joseph.olstad’s picture

Issue summary: View changes
joseph.olstad’s picture

Issue summary: View changes
joseph.olstad’s picture

Issue summary: View changes
joseph.olstad’s picture

Issue summary: View changes
joseph.olstad’s picture

Issue summary: View changes
joseph.olstad’s picture

Priority: Normal » Major

das-peter made their first commit to this issue’s fork.

joseph.olstad’s picture

Status: Active » Needs review

Test results look great.
testTranslationTokens is passing in all phpunit runs.

Unrelated fails in next minor.

das-peter’s picture

Came across the lack fo translation value access too - decided to give this a first go.

Went with the entity level switch: [entity:t:*] -> [entity:t:en] [entity:t:de] [entity:t:zh-hans] etc.
Reason is that the translation handling happens on entity level (->hasTranslation(), ->getTranslation()) so resolving as early as possible should hopefully help resolving in the requested language down the chain.

I didn't really like having floating language token on the entity - hence the t group - essentially an homage to t() :P.

Encountered some issues with the first draft that was re-using existing token types - switched to a dedicated translations token type per translatable entity type approach to work some of the encountered limitations.
Still doesn't feel ideal but I've no idea if that could be streamlined without causing more trouble than it's worth it.

Tests have been added and pass.
The pipeline failures about code style seem to refer to untouched code.

Reviews would be great.

@joseph.olstad Thanks for the quick response! You were faster than I could wrap up my comment :)

joseph.olstad’s picture

the :t: prefix is likely a good call, not too hard to figure out. Hopefully there's a helper for this in the token list.