Internationalization Strategies?

I have an inherited project that needs to have a French translation added. Right now it is an LMS type course built in Modx with a lot of resources being dependent on one another. TVs for example “Pr-requisite resource ID for this module” which holds a resource ID.

Rather than duplicating the entire context and editing every snippet call, snippet, chunk - create new templates for French etc-etc-etc I was wondering:

“Is it possible and what are the drawbacks of creating a plugin to change the system default locale on a session-by-session basis?”

Also using that plugin to detect browser locale/cookie settings and load some additional lexicons.

It would be nice:
Currently the course is in a sub directory that matches the client’s company name acronym say “/XYZ/”, it would also be really nice to be able to rewrite/alias that acronym/directory to the French version of the company name i.e.“/ABC/”

Any thoughts on if this is possible and how to go about it?

It’s possible to do that.

One drawback is, that a lot of the page content can’t be cached anymore. Every lexicon tag ([[!%lexicon_key]]) you use for the translation, has to be uncached to show the correct content for this specific request.


Not sure I understand correctly what you are trying to do here.
Is /XYZ/ a part of the URL that you are trying to replace? E.g. https://yourdomain.com/XYZ/somepage.html becomes https://yourdomain.com/ABC/somepage.html for the french version?

It would be easy to rewrite incoming requests to a different path.
The problem occurs when you try to create those links. Because every link tag (e.g. [[~1]]) on your page and every call to $modx->makeUrl(); returns the original path.

Interesting point about the cache issue … though it is not a public site and there will only be a couple thousand ‘students’ over the course of several months. So, I don’t see cache as my biggest problem (throw more ram at it :wink: )

One interesting thing I did find was additional lexicons need to be loaded after changing the cultureKey.

Also, the default cultureKey behavior seems to be set on every request and not bound to the modx session.

I have not been able to investigate or test how manually setting it VIA a plugin will affect other packages that use lexicons (login/register etc)

Here’s what I came up with so far.

<?php
/**
 * This needs to be run on OnWebPageInit or OnHandleRequest
 * $deafultCultureKey = $modx->getOption('cultureKey');
 * setcookie('myLocale', 'de', time() + (86400 * 365), "/"); 
 */
function loadLexicons($modx)
{
    $modx->lexicon->load('XYZ:default');
// we need to figure out a cleaner way of just loading the
//  lexicons we need as we need them
// $modx->lexicon->load('XYZ:moduleOne');
// $modx->lexicon->load('XYZ:moduleTwo');
};

// if a cookie is set use that
if(isset($_COOKIE['myLocale']) && in_array($_COOKIE['myLocale'], ['en','fr'])) {
    $modx->setOption('cultureKey',$_COOKIE['myLocale']);
    loadLexicons($modx);
    return;
}

// else use the browser locale
$browserLocale = trim(substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2));

if(isset($browserLocale) && in_array($browserLocale, ['en','fr'])){
    $modx->setOption('cultureKey',$browserLocale);
    loadLexicons($modx);
    return;
}
// $modx->setOption('cultureKey','zh');
// else use defaults and load the lexicons
loadLexicons($modx);

return;

Is the order that system events are fired in documented?

EDIT
Found it: https://docs.modx.com/3.x/en/extending-modx/plugins/system-events/onhandlerequest

So, now it looks like the big drawback is that I am basically loading the entire course content on every request. I think maybe adding a snippet to the resources in each module (to load only that module) should work.

Just spitballing here, but how about a snippet that pulls in the appropriate content chunk with $modx->getChunk(), based on a $_SESSION variable.

If the session variable holds the language name, the chunk can be named after the language combined with the page alias.

1 Like

Thanks Bob, I would up doing a couple different things… took a lesson from Laravel and implemented a “languages as strings” approach to deal with things like pagetitle, longtitle, etc. It can also handle items hard coded in the database.
The drawback here is that if someone edits a pagetitle… etc, I’ll have to create a plugin to handle updating the json file, also @inline chunks for getResource, wayfinder etc don’t work, tpl files need to be created.

<?php
/**
 * If the cultureKey is not 'fr' ... we don't need to do anything
 * Just echo the string back
 * [[!getLanguageByString? &translate=`[[*pagetitle]]`]]
 * [[!getLanguageByString? &translate=`[[*longtitle:default=`[[*pagetitle]]`]]`]]
 * 
 */
 /*
     /XYZ/core/components/courses/lexicon/language_strings.json
     {
     "Logout": "Déconnexion",
     "Dashboard": "Tableau de bord.",
     "Profile": "Profil",
     "Change Password": "Changer le mot de passe.",
     "Register": "S'inscrire.",
     }
  */
if($modx->getOption('cultureKey') == 'fr')
{
     $json = file_get_contents(MODX_BASE_PATH . '/XYZ/core/components/courses/lexicon/language_strings.json');
     $strings = json_decode($json, false);
     foreach($strings as $key => $value)
     {
          // we should look at making this not case sensitive .... 
          if(trim($key) == trim($scriptProperties['translate']))
          {
               echo trim($value);
               return;
          }
     }
     echo $scriptProperties['translate'];
}else{
     echo $scriptProperties['translate'];
}

return;

I can also load the lexicons for different modules on a per page basis, splitting them up into smaller files is definitely the way to go! It would probably be handy to make the &lexicon attribute able to handle an array (of lexicons) but this works for now.

<?php
// [[!loadModuleLexicon? &lexicon=`moduleOne`]]
$lexicon = isset($scriptProperties['lexicon']) ? $scriptProperties['lexicon'] : FALSE;
if(!$lexicon)
{
     return;
}
$modx->lexicon->load('courses:'.$lexicon);
return;

And the locale picker … I didn’t do anything with session, because “why” the cultureKey needs to be set on every request anyway, and if the browser isn’t accepting cookies … well, they can’t log in soooooo…

<?php
$redirectTo = $modx->resource->get('id');

$newLocale = isset($_GET['setLocale']) ? $_GET['setLocale'] : FALSE;

/**
 * Does the browser accecpt cookies?
 */
if (isset($_GET['cookieCheck'])) {
     if (!isset($_COOKIE['myLocale'])) {
          /**
           * throw an error if not.... 
           */
          $modx->log(modX::LOG_LEVEL_ERROR, 'Browser does not use cookies');

     }
}

/**
 * was setLocale part of the querystring? 
 * is it in the acceptable languages?
 */
if (isset($newLocale) && in_array($newLocale, ['en', 'fr'])) {
     // $modx->log(modX::LOG_LEVEL_ERROR, 'Setting cookie locale');
     /**
      * set the cookie
      */
     setcookie('myLocale', $newLocale, time() + (86400 * 365), "/"); // 86400 = 1 day

     /** 
      * redirect back to the same page with a cookie check
      * we have to redirect or else the  cookie won't be read
      */
     $url = $modx->makeUrl($redirectTo, '', array('cookieCheck' => 'cookie'), 'full');
     $modx->sendRedirect($url,array('type' => 'REDIRECT_REFRESH'));
}

echo $modx->getChunk('localePickerTpl', ['currentLocale' => $modx->getOption('cultureKey')]);

return;

/*

 <div class="locale-picker">
     [[!+currentLocale:is=`en`:then=`
     <a style="color:#999999; cursor: not-allowed;" >EN</a> | <a href="[[~[[*id]]]]&setLocale=fr">FR</a>
     `]]
     [[!+currentLocale:is=`fr`:then=`
     <a href="[[~[[*id]]]]&setLocale=en">EN</a> | <a style="color:#999999;cursor: not-allowed;">FR</a>
     `]]
 </div>
*/