Hello Everyone,

A quick appeal to those more familiar with the Multilingual capabilities of BCMS.

I have been running a Multilingual D7U3 website for over a decade.  

To me, Drupal appears to handle Multilingual websites quite differently than Unilingual sites.

Lately, I have found myself coding against this challenge a lot, using code like the following:

 # --------
 # Language
 # --------
 #
 # Here we have a bit of a "situation", because of the way Drupal handles
 # unilingual vs. multilingual sites, which works out to 3 scenarios:
 #
 #  1) If the site is Unilingual, there is NO need to specify a language.
 #     In fact, if a language IS specified (even while installed AND
 #     flagged as the *default* language) a 404 will result.
 #
 #  2) If the site is Multilingual, a language specifier MUST be used
 #     for every language aside from the default language.  Furthermore,
 #     if NO language is specified, the installed *default* language is
 #     assumed and used in a language specifier free URL context.  This
 #     means that nodes exist in two name spaces (A) A namespace where
 #     no language is specified; (B) A namespace where the *default*
 #     language is specified.  Both of the below examples resolve
 #     to the same web page in a Multilingual context where the
 #     *default* language is English (en):
 #     - FQDN/node/591
 #     - FQDN/en/node/591
 #
 #  3) If the site is Multilingual, the *default* language CAN be
 #     specified, leading to consistent URL handling:
 #     - FQDN/zh-hant/node/591
 #     - FQDN/zh-hans/node/591
 #     - FQDN/en/node/591

 global $language;

 # Multilingual?
 if (drupal_multilingual()) {

   # Multilingual site.  Set local current language variable, plus suffix
   $local_current_language = $language->language . '/';

 }
 else {

   # Unilingual site.  No local language.
   $local_current_language = '';

 }
 

It would be just great if BCMS was Multilingual-enabled from the ground up, which to me means that when a URL that includes a language specifier set to the installed and default language is sent to a Unilingual BCMS setup, BCMS does not produce a 404.

Which of the 3 scenarios described above does BCMS conform to?

g.
----

Comments

I don't think I follow everything in your query, but I do know that Backdrop inherited almost all of Drupal 7's multilingual capabilities for better or worse. Same goes for the Drupal 7 i18n module which was ported.

I suggest trying to recreate your scenario on a demo site https://backdropcms.org/demo

@herb,

Thanks for your speedy response.  

If I was unclear, I apologize.

Drupal has a flaw where a URL with a specified language (even if that language is the installed and default language) results in a 404.

I would like BCMS to resolve that URL to the right place, which is the same destination as the unadorned URL.

Ex:


A)  {fqdn}/en/node/1

B) {fqdn/node/1

I would like both URLs to not result in a 404.  

Currently, example A FAILS

As you suggested, I have tried this on:

  1. Stock Unilingual Drupal (FAILS)
  2. Stock Unilingual Backdrop (FAILS)
  3. Multilingual Drupal (PASSES)

I have not tried it on a Multilingual BCMS - which is beside the point.

What I am advocating for is a modification to CORE BCMS to make a Unilingual implementation more resilient when it comes to incoming URL that feature a language specifier which matches the default language.

This is in pursuit of more consistent URL processing and an "improved" product should BCMS be compared to Drupal in terms of Multilingual readiness.

I hope that clarifies things (did it?)

----

g.

tl;dr:  Did you know that every node carries within it the potential  to identify the "translation set" of which it is a member?

The stock Backdrop {node} table has the following schema:

MariaDB [backdrop]> show columns from node;
+------------------------+---------------------+------+-----+---------+----------------+
| Field                  | Type                | Null | Key | Default | Extra          |
+------------------------+---------------------+------+-----+---------+----------------+
| nid                    | int(10) unsigned    | NO   | PRI | NULL    | auto_increment |
| vid                    | int(10) unsigned    | YES  | UNI | NULL    |                |
| type                   | varchar(32)         | NO   | MUL |         |                |
| langcode               | varchar(12)         | NO   |     |         |                |
| title                  | varchar(255)        | NO   | MUL |         |                |
| uid                    | int(11)             | NO   | MUL | 0       |                |
| status                 | int(11)             | NO   | MUL | 1       |                |
| created                | int(11)             | NO   | MUL | 0       |                |
| changed                | int(11)             | NO   | MUL | 0       |                |
| scheduled              | int(11)             | NO   |     | 0       |                |
| comment                | int(11)             | NO   |     | 0       |                |
| promote                | int(11)             | NO   | MUL | 0       |                |
| sticky                 | int(11)             | NO   |     | 0       |                |
| tnid                   | int(10) unsigned    | NO   | MUL | 0       |                |
| translate              | int(11)             | NO   | MUL | 0       |                |
| comment_close_override | tinyint(3) unsigned | NO   |     | 0       |                |
+------------------------+---------------------+------+-----+---------+----------------+
 

tnid is a very interesting value.

It points to the default language node nid.  In the case of a node existing in the default language of the website, it just points back at itself.  For any other node tnid indicates the "base" node of the "translation set" of which it is a member.

Conceptually speaking, this is a pointer structure with the root referring to itself:

Especially interesting commands to play around with this data structure are:

node_load($nid)

To obtain the tnid value, and then:

translation_node_get_translations($tnid)

To get a list of the members of the translation set.

Here's the function definition.  

Look at @params to see the return type and format.

/**
* Gets all nodes in a given translation set.
*
* @param $tnid
*   The translation source nid of the translation set, the identifier of the
*   node used to derive all translations in the set.
*
* @return
*   Array of partial node objects (nid, title, language) representing all
*   nodes in the translation set, in effect all translations of node $tnid,
*   including node $tnid itself. Because these are partial nodes, you need to
*   node_load() the full node, if you need more properties. The array is
*   indexed by language code.
*/
function translation_node_get_translations($tnid) {
 if (is_numeric($tnid) && $tnid) {
   $translations = &drupal_static(__FUNCTION__, array());

   if (!isset($translations[$tnid])) {
     $translations[$tnid] = array();
     $result = db_select('node', 'n')
       ->fields('n', array('nid', 'type', 'uid', 'status', 'title', 'language'))
       ->condition('n.tnid', $tnid)
       ->addTag('node_access')
       ->execute();

     foreach ($result as $node) {
       $langcode = entity_language('node', $node);
       $translations[$tnid][$langcode] = $node;
     }
   }
   return $translations[$tnid];
 }
}
 

g.

----