Flow: translation of labels not working

Hallo,

as described here I want to use translations in my flow app.
My My.Package/Resources/Private/Templates/Member/New.html is

<f:layout name="Default" />

<f:section name="Title">New member</f:section>

<f:section name="Content">
    <p>Just fill out the following form to create a new member:</p>

	 <f:render partial="FormErrors" arguments="{for: 'newMember'}" />
	     
    <f:form action="create" objectName="newMember">
        <ul>
            <li>
                <label for="salutation"><f:translate id="member.salutation"/></label>
                <f:form.textfield property="salutation" id="salutation" />
            </li>
       </ul>
        <f:form.submit value="Create" />
    </f:form>
</f:section>

My My.Package/Resources/Private/Translations/en/Main.xlf is

<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
	<file original="" source-language="en" datatype="plaintext">
		<body>
			<trans-unit id="member.salutation" xml:space="preserve">
				<source>Salutation</source>
			</trans-unit>
		</body>
	</file>
</xliff>

And my My.Package/Resources/Private/Translations/de/Main.xlf is

<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
	<file original="" source-language="en" target-language="de" datatype="plaintext">
		<body>
			<trans-unit id="member.salutation" xml:space="preserve">
				<source>Salutation</source>
				<target>Anrede</target>
			</trans-unit>
		</body>
	</file>
</xliff>

In the frontend I get for both languages, so for englisch and for german this:

<li>
    <label for="salutation">Salutation</label>
    <input type="text" id="salutation" name="newMember[salutation]" />
</li>

But in german I would expect this:

<li>
    <label for="salutation">Anrede</label>
    <input type="text" id="salutation" name="newMember[salutation]" />
</li>

Any ideas what I’m doing wrong?

Hi, I can’t see anything wrong with your code.
But how do you set the current language in your controller (Or other place)?

Does it works when you define a package?

<f:translate id="salutation" package="Foo.Bar">Anrede</f:translate>

With

<f:translate id="member.salutation" package="My.Package">Anrede</f:translate>

I get “Anrede” in the frontend, but even also “Anrede” with language en, so the translation doesn’t work.

It also doesn’t work neither with

f:translate id="member.salutation" package="My.Package">Salutation</f:translate>

nor

<label for="salutation"><f:translate id="member.salutation" package="My.Package"/></label>

nor

<label for="salutation"><f:translate id="member.salutation" package="My. Package">Salutation</f:translate></label>

nor

<label for="salutation"><f:translate id="member.salutation" source="My.Package">Salutation</f:translate></label>

nor

<label for="salutation"><f:translate>Salutation</f:translate></label>

But how do you set the current language in your controller (Or other place)?

I did not set the current language in my controller or at another place. Do I have to? Do you have a hint, how?

So how do you switch between English and German?

Another try :wink: Do you have something like the following in your package configuration (Settings).

My:
  Package:
    translation:
      packageKey: 'My.Package'
      sourceName: 'Main'

And what happens when you render it this way.

<label for="salutation">
    <f:translate id="member.salutation" source="My.Package" />
</label>`

So how do you switch between English and German?

I thought flow would switch automatically the language based on the “Acccept-Language request HTTP header”.
So to test it, I switch between one browser which sends only the accept-language request header “de” and another browser which sends the accept-language request header “de,en-US;q=0.9,en;q=0.8”.

Do I therefore have to…

provide the AcceptLanguage HTTP header to the detectLocaleFromHttpHeader() method, which will analyze the header and return the best matching Locale object.

…by myself in my controller? Till now I thought that this is done automatically by flow by the Detector class.

Hallo Markus,

thank you very much for your further try.
With
<f:translate id="member.salutation" source="My.Package" />
I get “member.salutation” in frontend for both languages, also when I set your suggested Settings in the Settings.yaml of my package.
With
<label for="salutation"><f:translate id="member.salutation" package="My.Package"/></label>
I get again only “Salutation” in frontend for both languages.

No you can ask Flow to give you a locale that matches the user see https://flowframework.readthedocs.io/en/stable/TheDefinitiveGuide/PartIII/Internationalization.html#detecting-user-locale but then you have to tell Flow to use it.

The default behaviour is not to automatically switch the locale as this might not be what the dev wants.

I found an old example in slack which should help you. And you can also use the Detectors detectLocaleFromHttpHeader() method instead of the argument like in the example.

use Neos\Flow\I18n\Service as I18nService;
// ...
  /**
    * @Flow\Inject
    * @var I18nService
    */
  protected $localizationService;
  /**
    * @Flow\Inject
    * @var LocalesDetector
    */
  protected $localesDetector;
// ...
/**
  * @param ViewInterface $view
  * @throws NoSuchArgumentException
  */
protected function initializeView(ViewInterface $view)
{
    $localeArgument = null;
    if ($this->request->hasArgument('locale')) {
        $localeArgument = $this->request->getArgument('locale');
    }
    if (is_string($localeArgument)) {
        $detectedLocale = $this->localesDetector->detectLocaleFromLocaleTag($localeArgument);
    } else {
        $detectedLocale = $this->localesDetector->detectLocaleFromLocaleTag('');
    }
    $this->localizationService->getConfiguration()->setCurrentLocale($detectedLocale);
    $this->view->assign('locale', $detectedLocale);
    parent::initializeView($view);
}
1 Like

And you can build it into a http component to have it done outside of your controller - right?

And it could be done in the core and activated via a Configuration Settings? :smiley:

1 Like

Do we have a good example for that somewhere?
Should be something many people need.

Conceptual it’s done here (for Neos.Neos.) https://github.com/axovis/Axovis.Neos.LanguageDetection/blob/master/Classes/Axovis/Neos/LanguageDetection/LanguageDetectionComponent.php

But I would expect us to be able to do similar, and then “override” the Configuration object inside I18n\Service, which is currently set to a default depending on the Settings.yaml value

Would be great to have this component as tiny standalone package. Install and everything works as expected. But never looked deeper into the topic. Last time I needed to set the locale manually was years ago.

1 Like

I started adding something like this to Flow core a while ago, but never got around to finish it yet. It’s still on my TODO list and any help is highly welcome!

https://github.com/neos/flow-development-collection/pull/1264

Major point: How to integrate this into Neos CMS then?

1 Like

In my app there are only two languages. With a http component it’ s now working for me.
Thank you for your kind help!

Can you share your component for others to use?

Can you share your component for others to use?

With pleasure. Maybe it helps someone.

My.Package/Classes/Http/LanguageDetection/LanguageDetectionComponent.php:

<?php
namespace My\Package\Http\LanguageDetection;

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Http\Component\ComponentContext;
use Neos\Flow\Http\Component\ComponentInterface;
use Psr\Http\Message\ServerRequestInterface;


/**
 * A HTTP component that detects and sets the current locale from Accept-Language header.
 */
class LanguageDetectionComponent implements ComponentInterface 
{
	

	/**
	 * @var \Neos\Flow\I18n\Service
	 * @Flow\Inject
	 */
	protected $i18nService;
	
	/**
	 * @var GetLanguageHeader
    * @Flow\Inject
	 */
	protected $getLanguageHeader; 
	
	    
	/**
	 * @param ServerRequestInterface $httpRequest
	 * @return Locale
	 */
	protected function retrieveLocaleFromRequest(ServerRequestInterface $httpRequest)
	{
		$language = $this->getLanguageHeader->parseLanguageHeader($httpRequest->getHeader('Accept-Language'));
		$locale = new \Neos\Flow\I18n\Locale($language); 
		return $locale;
	}
	

	/**
	 * @param ComponentContext $componentContext
	 * @return void
	 */
	public function handle(ComponentContext $componentContext)
	{
		$httpRequest = $componentContext->getHttpRequest();
		$detectedLocale = $this->retrieveLocaleFromRequest($httpRequest);
        $this->i18nService->getConfiguration()->setCurrentLocale($detectedLocale);
	}
}

My.Package/Classes/Http/LanguageDetection/GetLanguageHeader.php:

<?php
namespace My\Package\Http\LanguageDetection;

/*
 * This file is part of the My.Package package.
 *
 */

use Neos\Flow\Annotations as Flow;

/**
 * The Utility class for locale specific actions
 *
 * @Flow\Scope("singleton")
 */
class GetLanguageHeader
{
	
	/**
	* A pattern which matches HTTP Accept-Language Headers
   */
	const PATTERN_MATCH_ACCEPTLANGUAGE = '/([a-z]{1,8}(-[a-z]{1,8})?|\*)(;q=(1|0(\.[0-9]+)?))?/i';

	/**
	* Parses Accept-Language header and returns 'de' if Accept-Language header contains 'de', otherwise 'en'
	* Returns 'de' if there is no Accept-Language header
	*
	*
	* @param string $acceptLanguageHeader
	* @return string The language 
	*/
	public static function parseLanguageHeader($acceptLanguageHeader)
	{
   	  $acceptLanguageHeader = str_replace(' ', '', $acceptLanguageHeader);
				
		// if there is a language header
      if (preg_grep(self::PATTERN_MATCH_ACCEPTLANGUAGE, $acceptLanguageHeader) !== false) {
      	foreach ($acceptLanguageHeader as $localeIdentifier) {
      		// if language header contains 'de' or '*', return de; else return 'en'
				$de = 'de';
				$all = '*';
				$find = strpos ($localeIdentifier, $de);
				$findAll = strpos ($localeIdentifier, $all);
				
				if( $find !== false || $findAll !== false ) {
					return 'de';
				} else {
					return 'en';	
				}
			}
		}
		// if no language header, return 'de'
		return 'de';
	}
}
2 Likes

@aberl There we have the component for the Flow Core :heart_eyes:

Did you know, there’s also a I18n Detector in the Flow core (see https://github.com/neos/flow-development-collection/blob/master/Neos.Flow/Classes/I18n/Detector.php). I didn’t compare the code, but that was meant to be used from HTTP components, too