<?xml version="1.0" encoding="utf-8"?>
<!-- generator="Kirby" -->
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">

  <channel>
    <title>Mot-cl&#233;: i18n &#183; Blog &#183; Liip</title>
    <link>https://www.liip.ch/fr/blog/tags/i18n</link>
    <generator>Kirby</generator>
    <lastBuildDate>Wed, 12 Apr 2017 00:00:00 +0200</lastBuildDate>
    <atom:link href="https://www.liip.ch" rel="self" type="application/rss+xml" />

        <description>Articles du blog Liip avec le mot-cl&#233; &#8220;i18n&#8221;</description>
    
        <language>fr</language>
    
        <item>
      <title>Drupal 8 &#8211; Multilanguage Improvements</title>
      <link>https://www.liip.ch/fr/blog/drupal-8-multilanguage-improvements</link>
      <guid>https://www.liip.ch/fr/blog/drupal-8-multilanguage-improvements</guid>
      <pubDate>Wed, 12 Apr 2017 00:00:00 +0200</pubDate>
      <description><![CDATA[<p>As a Swiss-based Drupal Agency, we have to create a lot of multilingual sites. Since Switzerland has three official languages (German, French, Italian) and even one more national language (Rumantsch), we are used to this requirement and we found our way with Drupal to make this an easy task (usually). We mainly used node translations in Drupal 7 for maximum flexibility. We used to separate languages from each other using the various i18n modules, language specific menus, blocks, URL-patterns, terms and so on.</p>
<p>With Drupal 8, things changed.</p>
<p>I struggled a little doing multilingual sites in Drupal 8 the same way I was used to in Drupal 7 because node translation is not available anymore (which is good) so I had to find another way to achieve the same easy to handle translations system. For us and for our clients. Let me explain, what I have learned.</p>
<figure><img src="https://liip.rokka.io/www_inarticle/c98a72751151c90efcbb09843a1e11b8bf6b536d/maxresdefault-1024x575.jpg" alt="Drupal 8 multilanguage"></figure>
<p><em>Image: drupal8multilingual.org</em></p>
<h2>Drupal 8 <del>issues</del> multilanguage challenges</h2>
<h3>Challenge 1: Node add / edit menu handling</h3>
<p>The main challenge I had using Drupal 8, was the ease to build your menus directly from the node creation page. You can do it, but only for the initial language. If you try to add a translated node to another menu or rename the item, it always ends up moving / renaming the source node instead of adding a link to the translation. So it can become quite confusing building a navigation directly from the node creation page or to add translations to the menu. A workaround was to add all navigation items manually in the menu administration if you are using a menu per language. With lots of languages and menus / items, this is not really a convenient task. Fortunately, translations from the node creation page have been implemented with a later release of Drupal 8.</p>
<h3>Challenge 2: Untranslated Nodes show up in Menu</h3>
<p>Another thing which bothered me was that untranslated nodes show up in the navigation (if you use only one menu). This can be quite confusing since most of the times not every page is translated in every language. Or in some languages, you need a little more than in others. You can read a lot about this topic and the reasons behind (e.g. <a href="http://hojtsy.hu/blog/2015-nov-18/drupal-8-multilingual-tidbits-20-combination-use-cases-content-and-menus">here</a> and <a href="https://www.drupal.org/node/2466553">here</a>). However you do it, it's always wrong in some situations and perfectly fine in others. But to be “limited” and “locked in” to a certain way is not nice and you have to deal with it. To sum up, once a node is put into a menu, it will show up everywhere. Regardless if there are translations or not.</p>
<h3>Challenge 3: Language Switcher shows all languages – always.</h3>
<p>Somewhat confusing is the Language Switcher. In Drupal 7, a language link was not available or strikethrough if there was no translation available. In Drupal 8, every language is always visible and linked. So if you look on a German page which is only available in German, the language switcher will present you all language links to the same node. A click on those language links mainly changes the interface language but the node content remains the same (since not translated). Usually also with a drupalish URL (node/xxxx) because there is no translation for the node and therefore also no URL alias available. This behavior is confusing and wrong in my point of view</p>
<p>An example to illustrate the above-written challenges.</p>
<figure><img src="https://liip.rokka.io/www_inarticle/5329af720a3db73e995bc721779f81764ecae8a5/welcome-to-drupal-8-3-0-drupal-8-3-01-1.jpg" alt="multilanguage issues with Drupal 8"></figure>
<p><em>English Front-Page with mixed navigation items.</em></p>
<p>The screen above shows an installation with 2 languages (English and German). The <strong>English Page</strong> is a basic page which has a translation. English is selected. If you choose <strong>Deutsch</strong> on the language switcher, the <strong>English Page</strong> becomes <strong>Deutsche Seite</strong> (see image below) and shows the German content. So far so good. But the second menu item you see with the title <strong>Über uns (nur Deutsch)</strong> should not appear here since it's only available in German. But it does. And if you actually go on this page, you will see the German text with everything English around it and no URL-Alias (/node/2 in this example). This is usually not very useful for us.</p>
<p><em>German only Page – Language Switcher visible. </em></p>
<p>Also, the language switcher shown in the image above is from my point of view wrong or not very useful. It shows a link to the English version, but there is no English translation for this node. So why is it there? To see a German page with English decoration? Not sure. But I want to get rid of this link or at least modify it to be stroked through if the language is not available.</p>
<h2>How to <del>fix</del> improve this?</h2>
<p>Luckily, the Drupal community is always good for help. After some “research” on the web, I finally found (besides lots of discussions and comments in the issue queues) a way to achieve the desired setup.</p>
<p>To sum up again: <em>I want to see only menu items which are available in my language and only see a link to another language, if a translation is available.</em></p>
<p>Since there is no patch and still some ongoing discussions on <a href="https://www.drupal.org">drupal.org</a> you need to implement it on your own. Implement the following two modules.</p>
<h3>Hide untranslated menu items</h3>
<p>Code from <a href="https://www.drupal.org/node/2466553#comment-11991690">drupal.org/node/2466553#comment-11991690</a>. Credits go to michaelkoehne.</p>
<pre><code class="language-php">&lt;?php

use Drupal\Core\Menu\MenuLinkInterface;
use Drupal\menu_link_content\Plugin\Menu\MenuLinkContent;
use Drupal\Core\Language\LanguageInterface;

/**
 * Implements hook_preprocess_menu().
 */
function MYMODULE_preprocess_menu(&amp;$variables) {
  if ($variables['menu_name'] == 'main') {
    $language = Drupal::languageManager()
      -&gt;getCurrentLanguage(LanguageInterface::TYPE_CONTENT)
      -&gt;getId();
    foreach ($variables['items'] as $key =&gt; $item) {
      if (!$variables['items'][$key] = MYMODULE_checkForMenuItemTranslation($item, $language)) {
        unset($variables['items'][$key]);
      }
    }
  }
}

function MYMODULE_checkForMenuItemTranslation($item, $language) {
  $menuLinkEntity = MYMODULE_load_link_entity_by_link($item['original_link']);

  if ($menuLinkEntity != NULL) {
    $languages = $menuLinkEntity-&gt;getTranslationLanguages();

    // Remove links which are not translated to the current language.
    if (!array_key_exists($language, $languages)) {
      return FALSE;
    }
    else {
      if (count($item['below']) &gt; 0) {
        foreach ($item['below'] as $subkey =&gt; $subitem) {
          if (!$item['below'][$subkey] = MYMODULE_checkForMenuItemTranslation($subitem, $language)) {
            unset($item['below'][$subkey]);
          }
        }
      }
      return $item;
    }

  }
}

function MYMODULE_load_link_entity_by_link(MenuLinkInterface $menuLinkContentPlugin) {
  $entity = NULL;
  if ($menuLinkContentPlugin instanceof MenuLinkContent) {
    $menu_link = explode(':', $menuLinkContentPlugin-&gt;getPluginId(), 2);
    $uuid = $menu_link[1];
    $entity = \Drupal::service('entity.repository')
      -&gt;loadEntityByUuid('menu_link_content', $uuid);
  }
  return $entity;
}</code></pre>
<h3>Hide untranslated languages in language switcher</h3>
<p>Code from <a href="https://www.drupal.org/node/2791231#comment-12004615">drupal.org/node/2791231#comment-12004615</a> (slightly adapted. Links get a class, not removed by default). Credits to Leon Kessler.</p>
<pre><code class="language-php">&lt;?php

/**
 * @file
 * Hide language switcher links for untranslated languages on an entity.
 */
use Drupal\Core\Entity\ContentEntityInterface;

/**
 * Implements hook_language_switch_links_alter().
 */
function MYOTHERMODULE_language_switch_links_alter(array &amp;$links, $type, $path) {
  if ($entity = MYOTHERMODULE_get_page_entity()) {
    $new_links = array();
    foreach ($links as $lang_code =&gt; $link) {
      try {
        if ($entity-&gt;getTranslation($lang_code)-&gt;access('view')) {
          $new_links[$lang_code] = $link;
        }
      }
      catch (\InvalidArgumentException $e) {
        // This language is untranslated so do not add it to the links.
        $link['attributes']['class'][] = 'not-translated';
        $new_links[$lang_code] = $link;
      }

    }
    $links = $new_links;

    // If we're left with less than 2 links, then there's nothing to switch.
    // Hide the language switcher.
    if (count($links) &lt; 2) {
      $links = array();
    }
  }
}

/**
 * Retrieve the current page entity.
 *
 * @return Drupal\Core\Entity\ContentEntityInterface
 *   The retrieved entity, or FALSE if none found.
 */
function MYOTHERMODULE_get_page_entity() {
  $params = \Drupal::routeMatch()-&gt;getParameters()-&gt;all();
  $entity = reset($params);
  if ($entity instanceof ContentEntityInterface) {
    return $entity;
  }
  return FALSE;
}</code></pre>
<p>Please note: The code above is from Drupal.org and therefore thanks to the original authors linked above.</p>
<p>Enable those two modules and you're all set!</p>
<p>I did not encounter any issues yet using those two modules. If ever something changes in the way Drupal handles those cases, you just need to switch off the modules and everything should be back to normal. So nothing to lose right?</p>
<p>There are other attempts to this by altering the menu block. One of them is <a href="https://www.drupal.org/project/menu_block_current_language">Menu Block Current Language</a> but I had no luck with this one. On my most recent project, it worked with one menu but not if you separate your menu by two blocks (different starting levels).</p>
<p>I would love to hear how you guys handle those cases or how you deal with I18N in general. I'm sure there are a gazillion other ways to do it.</p>]]></description>
                  <enclosure url="http://liip.rokka.io/www_card_2/f9211b40340b5371365ffb6e71bec9904823864b/maxresdefault.jpg" length="69997" type="image/jpeg" />
          </item>
        <item>
      <title>Easy Storyboard translation in Xcode with Swift3</title>
      <link>https://www.liip.ch/fr/blog/storyboard-translation</link>
      <guid>https://www.liip.ch/fr/blog/storyboard-translation</guid>
      <pubDate>Thu, 10 Nov 2016 00:00:00 +0100</pubDate>
      <description><![CDATA[<p><em>In this post I explain how we created a modular library. Today we are happy to release and open source this lib. You can find the code <a href="https://github.com/liip/LiipKit">on GitHub</a>. Our objective was to have one set of translation files that could be used in the storyboard and Swift</em>.</p>
<p>In our multilingual iOS projects, we always struggled to translate storyboards in Xcode. We checked all around <a href="https://cocoapods.org/">CocoaPods</a>, but couldn't find any efficient solution so far. Every time, we ended up with multiple versions of the storyboard along with multiple versions of ‘Localizable.strings'. It was hard to keep everything under control, specially when translations needed to be updated throughout all files.</p>
<p>During our last mobile project, we asked ourselves: “What if we could only have one set of translation files that could be used in the storyboard and Swift?”. We brainstormed, and created a modular library to handle this painful task. Today we are happy to release and open source this lib. You can find the code <a href="https://github.com/liip/LiipKit">on GitHub</a>.</p>
<h2>Introducing LiipKit</h2>
<h3>Translation</h3>
<p>Our main requirement was to have only one set of translation files (i.e. one for each language). This should work on two aspects:</p>
<ol>
<li>Get a translation from Swift</li>
<li>Translate a UI component from the storyboard</li>
</ol>
<p>And this is how it works:</p>
<p><strong>Swift translation importer</strong> </p>
<pre><code>import LiipKit
let my_test_translation = ~"test"</code></pre>
<p><strong>Translation within the storyboard</strong> </p>
<p>UIButton, UIBarItem and UILabel have a new attribute “Localized Title”.</p>
<figure><img src="https://liip.rokka.io/www_inarticle/4d4c3cb90c484b9cfea3e3c269064434d926bee5/liipkit-storyboard.jpg" alt="New attribute for Button, BarItem and Label in Storyboard"></figure>
<h2>There is more to come</h2>
<p>On top of the translation module, we already implemented <a href="https://github.com/liip/LiipKit/blob/master/README.md#documentation">helpers on String, Int, and Date</a>. We're constantly adding new features to our LiipKit library, so be sure to watch the <a href="https://github.com/liip/LiipKit">GitHub repository</a>.</p>
<p>We welcome any input, feedback, or pull request.</p>]]></description>
                  <enclosure url="http://liip.rokka.io/www_card_2/4d4c3cb90c484b9cfea3e3c269064434d926bee5/liipkit-storyboard.jpg" length="39387" type="image/png" />
          </item>
        <item>
      <title>Multilanguage support for Doctrine PHPCR-ODM</title>
      <link>https://www.liip.ch/fr/blog/multilanguage-support-for-doctrine-phpcr-odm</link>
      <guid>https://www.liip.ch/fr/blog/multilanguage-support-for-doctrine-phpcr-odm</guid>
      <pubDate>Sun, 18 Dec 2011 00:00:00 +0100</pubDate>
      <description><![CDATA[<p>Over the last weeks, Dan, Brian and myself worked on <a href="https://github.com/doctrine/phpcr-odm/pull/81">adding translation capabilities</a> to <a href="https://github.com/doctrine/phpcr-odm/">Doctrine PHPCR-ODM</a>. PHPCR-ODM is an object – document mapper for the php content repository ( <a href="http://phpcr.github.com/">PHPCR</a>). Thanks to the <a href="http://www.liip.ch/en/who/news/archive/2011/11/28/innovation-at-liip-from-the-inside.html">Liip Ecostar</a> process, we got funding to do this during work time.</p>
<h3>How does it work?</h3>
<p>Using persistTranslation($document, $locale) or persist($document) with the @Locale annotation allows to store several copies of the “same” document in different languages. You update the document for the next language and then persist that translation too. Using find() to get a translated document, the LocaleChooserStrategy class will find the best (according to its implementation – might look at session locale, browser preferences, user account settings, …) available translation for a document. And using findTranslation() you can explicitly specify which language you want.</p>
<p>There are two translation strategies available: Store translated fields in a separate namespace as properties of the same node, and namespaced child nodes per locale with the translated properties in them. You can add your own strategies with the DocumentManager::addTranslationStrategy() method. Translation always happens on a document level, not on individual fields to keep performance reasonable.</p>
<p>Our translation strategies use a namespace to avoid collisions with other attributes resp. child nodes. In order to use multilanguage, <a href="https://github.com/doctrine/phpcr-odm/">set up the console</a> and run</p>
<pre><code>php bin/phpcr doctrine:phpcr:register-system-node-types</code></pre>
<p>.</p>
<p>When using findTranslation, the existing document instance is updated rather than a new one created. Otherwise we could get into non-deterministic situations when you update non-translated fields on two objects.</p>
<p>A complete example how using the translations looks like, using the default configured LocaleChooser:</p>
<pre><code>use DoctrineODMPHPCRMappingAnnotations as PHPCRODM;

/**
 * @PHPCRODMDocument(alias="translation_article", translator="attribute")
 */
class Article
{
    /** @PHPCRODMId */
    public $id;

    /**
     * The language this document currently is in
     * @PHPCRODMLocale
     */
    public $locale = 'en';

    /**
     * Untranslated property
     * @PHPCRODMDate
     */
    public $publishDate;

    /**
     * Translated property
     * @PHPCRODMString(translated=true)
     */
    public $topic;

    /**
     * Language specific image
     * @PHPCRODMBinary(translated=true)
     */
    public $image;
}

$localePrefs = array(
    'en' =&gt; array('en', 'fr'),
    'fr' =&gt; array('fr', 'en'),
);

$dm = new DoctrineODMPHPCRDocumentManager($session, $config);
$dm-&gt;setLocaleChooserStrategy(new LocaleChooser($localePrefs, 'en'));

// then to use translations:

$doc = new Article();
$doc-&gt;id = '/my_test_node';
$doc-&gt;publishedDate = new DateTime();
$doc-&gt;topic = 'An interesting subject';
$doc-&gt;image = fopen('english.jpg');

// Persist the document in English
$this-&gt;dm-&gt;persistTranslation($this-&gt;doc, 'en');

// Change the content and persist the document in French
$this-&gt;doc-&gt;topic = 'Un sujet intéressant';
$doc-&gt;image = fopen('english.jpg');
$this-&gt;dm-&gt;persistTranslation($this-&gt;doc, 'fr');

// Flush to write the changes to the phpcr backend
$this-&gt;dm-&gt;flush();

// Get the document in default language (English if you bootstrapped as in the example)
$doc = $this-&gt;dm-&gt;find('DoctrineTestsModelsTranslationArticle', '/my_test_node');
echo $doc-&gt;topic;

// Get the document in French (updates the existing document)
$this-&gt;dm-&gt;find('DoctrineTestsModelsTranslationArticle', '/my_test_node', 'fr');
echo $doc-&gt;topic;</code></pre>
<h3>What was added?</h3>
<p>The complete overview is in the <a href="https://github.com/doctrine/phpcr-odm/pull/81">pull request</a>. Just a summary:</p>
<ul>
<li>New annotation options:
<ul>
<li>translator=attribute|child|your_own is a new attribute for the @Document annotation, to mark a document as being translated and specify how translations are to be stored</li>
<li>@Locale makes a class property hold the locale the document is currently translated to.</li>
<li>translated=true is a new attribute for all properties to mark the String, Number, Binary and so on as being translated.</li>
</ul></li>
<li>TranslationStrategy is used for the translator attribute of the document. Default strategies for attribute and child exist.</li>
<li>LocaleChooserStrategy is used to determine what language matches best for doing language fallback, with the possibility to do language fallbacks based on your self defined logic. The default strategy is configurable with an ordered list of locales per requested locale</li>
<li>DocumentManager::persistTranslation($document, $locale) save document in that language</li>
<li>DocumentManager::findTranslation($className, $id, $locale) load a document in the specified locale (instead of the default one)</li>
<li>DocumentManager::getLocalesFor($document) (get the list of locales this document currently exists in)</li>
</ul>]]></description>
          </item>
    
  </channel>
</rss>
