Skip to content

Commit

Permalink
Add support for configurable "parent language behaviour"
Browse files Browse the repository at this point in the history
Now the plugin offers a setting to configure how the filter will
behave, with respect to parent languages, when processing a language
block.

This should help address issues #25 #42, but should be tested by the
affected parties before we can release a new version of the
plugin (hence the -alpha-1 release version).
  • Loading branch information
iarenaza committed Oct 28, 2024
1 parent 2d68c00 commit a462d85
Show file tree
Hide file tree
Showing 11 changed files with 336 additions and 29 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Added a setting to configure the parent languages behaviour (see README.md for additional details on the setting.

## [2.0.4] - 2024.10.18

### Fixed
Expand Down
45 changes: 29 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,41 @@ CI](https://github.com/iarenaza/moodle-filter_multilang2/workflows/Moodle%20plug

# To Use it #
- Create your contents in multiple languages.
- Enclose every language content between {mlang NN} and {mlang} tags:
- Enclose every language content between `{mlang XX}` and `{mlang}` tags (also known as a "language blocks"):
<pre>
{mlang XX}content in language XX{mlang}
{mlang YY}content in language YY{mlang}
{mlang other}content for other languages{mlang}</pre>
- where **XX** and **YY** are the Moodle short names for the language packs (i.e., en, fr, de, etc.) or the special language name 'other'.
- where **XX** and **YY** are the Moodle short names for the language packs (i.e., `en`, `en_CA`, `en_kids`, `fr_CA`, `de`, etc.) or the special language name `other`.
- **Version 1.1.1** and later: a new enhanced syntax to be able to specify multiple languages for a single tag is now available. Just specify the list of the languages separated by commas:
<pre>
{mlang XX,YY,ZZ}Text displayed if current lang is XX, YY or ZZ, or one of their parent laguages.{mlang}</pre>
{mlang en,es,fr_CA}Text displayed if current language is en, es or fr_CA, or one of their parent laguages.{mlang}</pre>
- Test it (by changing your browsing language in Moodle).

# How it works #
- Look for "lang blocks" in the text to be filtered.
- For each "lang block":
## Default behaviour ##
- Look for "language blocks" in the text to be filtered.
- For each "language block":
- If there are texts in the currently active language, print them.
- Else, if there exist texts in the current parent language, print them.
- Else, as fallback, print the text with language 'other' if such
one is set.
- Text outside of "lang blocks" will always be shown.
- Else, if there exist texts in the parent language(s) of the currently active language, unless the parent language is 'en', print them. This behaviour is configurable in version 2.0.5 and later (see "Configurable parent languages behaviour" below).
- Else, as fallback, print the text with language 'other' if such one is set.
- Else, don't print any text inside the language block.
- Text outside of "language blocks" will always be shown.

## Definition of "lang block" ##
Is any text (including spaces, tabs, linefeeds or return characters) placed between '{mlang XX}' and '{mlang}' markers. You can not only put text inside "lang block", but also images, videos or external embedded content. For example, this is a valid "lang block":
## Configurable parent languages behaviour ##

Since version 2.0.5, the plugin offers a setting to configure how the filter will behave, with respect to parent languages, when processing a language block.

As described in the previous section, when the filter checks whether a language block has to be displayed or not, it tries to match the languages specified in the block with the current language used by the user displaying the content. This matching can be done in three different ways, that the filter calls _parent languages behaviour_:

* **Always use parent languages, excluding 'en'**. _This is the traditional behaviour of the plugin_, and is the default value for the setting. When this behaviour is selected, the filter uses both the languages specified in the language block, and all the parents of those languages (recursively to the top), to match the current language used by the user. But the English language ('en') is never considered a parent language in this case, and is removed from the parent list. E.g., a language block that specifies 'en_kids' in the language list _will not_ be displayed if the current language used by the user displaying the content is 'en'. Notice that the English language is still used by the filter if it is _explicitly_ specified in the language blocks (e.g., `{mlang en}This text will be shown when the current language used by the user displaying the content is 'en'{mlang}`).

* **Always use parent languages, including 'en'**. Which works as **Always use parent languages, excluding 'en'**, but does not remove the English ('en') language from the parent languages list. E.g., a language block that specifies 'en_kids' in the language list _will be_ displayed if the current language used by the user displaying the content is 'en'.

* **Never use parent languages**. As the name implies, parent languages are never used to match the current language used by the user displaying the content. The filter restricts itself to the languages specified in the language block.

## Definition of "language block" ##
Is any text (including spaces, tabs, linefeeds or return characters) placed between '{mlang XX}' and '{mlang}' markers. You can not only put text inside "language block", but also images, videos or external embedded content. For example, this is a valid "language block":

<pre>
{mlang es,es_mx,es_co}
Expand All @@ -59,21 +72,21 @@ Fourth paragraph of text. Fourth paragraph of text. Fourth paragraph of text.
This text:
<pre>
{mlang other}Hello!{mlang}{mlang es,es_mx}¡Hola!{mlang}
This text is common for all languages because it's outside of all lang blocks.
This text is common for all languages because it's outside of all language blocks.
{mlang other}Bye!{mlang}{mlang it}Ciao!{mlang}</pre>
- If the current language is any language except "Spanish International", "Spanish - Mexico" or Italian, it will print:
<pre>
Hello!
This text is common for all languages because it's outside of all lang blocks.
This text is common for all languages because it's outside of all language blocks.
Bye!</pre>
- If the current language is "Spanish International" or "Spanish - Mexico", it will print:
<pre>
¡Hola!
This text is common for all languages because it's outside of all lang blocks.</pre>
This text is common for all languages because it's outside of all language blocks.</pre>
- Notice the final 'Bye!' / 'Ciao!' is not printed.
- If the current language is Italian, it will print:
<pre>
This text is common for all languages because it's outside of all lang blocks.
This text is common for all languages because it's outside of all language blocks.
Ciao!</pre>
- Notice the leading 'Hello!' / '¡Hola!' and the final 'Bye!' are not printed.

Expand All @@ -83,7 +96,7 @@ We create a label with the content shown in the following image:

<img src="https://moodle.org/pluginfile.php/50/local_plugins/plugin_screenshots/1560/multilang-example-1-modificado.png" height="694" width="800" alt="Multi-Language content in Spanish, English, an language-independent content" />

The "lang block" tags are highlighted using blue boxes. You can see that we have three pieces of content: the Spanish-only content (light yellow box), the language-independent content (light blue) and the English-only content (light red).
The "language block" tags are highlighted using blue boxes. You can see that we have three pieces of content: the Spanish-only content (light yellow box), the language-independent content (light blue) and the English-only content (light red).

If the user browses the page with English as her configured language, she will see the common content (light blue box) and the English-only content (light red):

Expand Down
65 changes: 58 additions & 7 deletions classes/text_filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@
* The way the filter works is as follows:
*
* - look for multilang blocks in the text.
* - if there exists texts in the currently active language, print them.
* - else, if there exists texts in the current parent language, print them.
* - else, if there exists texts in the language 'other', print them.
* - if there exist texts in the currently active language, print them.
* - else, if there exist texts in the parent language(s) of the
* currently active language, unless the parent language is 'en',
* print them (default "parent language behaviour", this is now
* configurable)
* - else, if there exist texts in the language 'other', print them.
* - else, don't print any text inside the lang block (this is a change
* from previous filter versions behaviour!!!!)
*
Expand Down Expand Up @@ -77,6 +80,11 @@ class text_filter extends \filter_multilang2_base_text_filter {
*/
protected static $parentcache;

/**
* @var object An instance of the string_manager class.
*/
protected static $stringmanager;

/**
* @var string The langauge we are currently using to filter multilang blocks.
* It can be either the user current language, or the language 'other'
Expand All @@ -91,6 +99,19 @@ class text_filter extends \filter_multilang2_base_text_filter {
*/
protected $replacementdone;

/**
* @var string What parent languages behaviour is configured for the plugin.
*/
protected $parentlangbehaviour;

/**
* Constructor - Make sure we use the configured parent languages behaviour.
*/
public function __construct() {
$this->parentlangbehaviour = get_config('filter_multilang2', 'parentlangbehaviour');
self::$stringmanager = get_string_manager();
}

/**
* Filter text before changing format to HTML.
*
Expand Down Expand Up @@ -141,6 +162,21 @@ public function filter_stage_string(string $text, array $options): string {
return $this->filter($text, $options);
}

/**
* Reset the parent languages cache.
*
* We need to reset the parent languages cache every time we
* switch from one parent languages behaviour to another. Because
* different behaviours may build different parent languages
* hierarchies.
*
* Specially useful (but not only) for unit tests.
*
*/
public static function reset_parentcache () :void {
self::$parentcache = ['other' => []];
}

/**
* This function filters the received text based on the language
* tags embedded in the text, and the current user language or
Expand All @@ -163,13 +199,24 @@ public function filter($text, array $options = []) {
}

if (!isset(self::$parentcache)) {
self::$parentcache['other'] = [];
self::reset_parentcache();
}

$this->replacementdone = false;
$currlang = current_language();
if (!array_key_exists($currlang, self::$parentcache)) {
$parentlangs = get_string_manager()->get_language_dependencies($currlang);
if ($this->parentlangbehaviour === 'never') {
self::reset_parentcache();
} else if (!array_key_exists($currlang, self::$parentcache)) {
$parentlangs = self::$stringmanager->get_language_dependencies($currlang);
if ($this->parentlangbehaviour === 'include_en') {
if (count($parentlangs) > 0) {
$realrootisen = self::$stringmanager->get_string('parentlanguage', 'langconfig',
null, $parentlangs[0]);
if ($realrootisen === 'en') {
$parentlangs = array_merge(array('en'), $parentlangs);
}
}
}
self::$parentcache[$currlang] = $parentlangs;
}

Expand Down Expand Up @@ -230,7 +277,11 @@ protected function replace_callback($replacelang, $langblock): string {
*/
$blocklangs = explode(',', str_replace(' ', '', str_replace('-', '_', strtolower($langblock[1]))));
$blocktext = $langblock[2];
$parentlangs = self::$parentcache[$replacelang];
if ($this->parentlangbehaviour === 'never') {
$parentlangs = array();
} else {
$parentlangs = self::$parentcache[$replacelang];
}
foreach ($blocklangs as $blocklang) {
/* We don't check for empty values of $blocklang as they simply don't
* match any language and they don't produce any errors or warnings.
Expand Down
35 changes: 35 additions & 0 deletions lang/en/filter_multilang2.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,40 @@
defined('MOODLE_INTERNAL') || die();

$string['filtername'] = 'Multi-Language Content (v2)';
$string['parentlangbehaviour'] = 'Parent languages behaviour';
$string['parentlangbehaviour_desc'] = '<p>When the filter checks whether
a language block has to be displayed or not, it tries to match the
languages specified in the block with the current language used by the
user displaying the content. This matching can be done in three
different ways, that the filter calls <em>parent languages
behaviour</em>:</p>
<ul>
<li><b>Always use parent languages, excluding \'en\'</b>. <em>This is
the traditional behaviour of the plugin</em>, and is the default value
for the setting. When this behaviour is selected, the filter uses both
the languages specified in the language block, and all the parents of
those languages (recursively to the top), to match the current
language used by the user. But the English language (\'en\') is never
considered a parent language in this case, and is removed from the
parent list. E.g., a language block that specifies \'en_kids\' in the
language list will not be displayed if the current language used by
the user displaying the content is \'en\'. Notice that the English
language is still used by the filter if it is <em>explicitly</em>
specified in the language blocks (e.g., <small><tt>{mlang en}This text will
be shown when the current language used by the user displaying the
content is \'en\'{mlang})</tt></small>.</li>
<li><b>Always use parent languages, including \'en\'</b>. Which works
as "Always use parent languages, excluding \'en\'", but does not
remove the English (\'en\') language from the parent languages
list. E.g., a language block that specifies \'en_kids\' in the
language list <em>will be</em> displayed if the current language used
by the user displaying the content is \'en\'.</li>
<li><b>Never use parent languages</b>. As the name implies, parent
languages are never used to match the current language used by the
user displaying the content. The filter restricts itself to the
languages specified in the language block.</li> </ul>';
$string['parentlangdefault'] = 'Always use parent languages, excluding \'en\' (traditional behaviour).';
$string['parentlangalwaysen'] = 'Always use parent languages, including \'en\'.';
$string['parentlangnever'] = 'Never use parent languages.';
$string['pluginname'] = 'Multi-Language Content (v2) Filter';
$string['privacy:metadata'] = 'The Multi-Language Content (v2) Filter plugin does not store any personal data.';
42 changes: 41 additions & 1 deletion lang/es/filter_multilang2.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,44 @@
defined('MOODLE_INTERNAL') || die();

$string['filtername'] = 'Contenido Multi-Idioma (v2)';
$string['pluginname'] = 'Filtro de Contenido Multi-Idioma (v2)';
$string['parentlangbehaviour'] = 'Comportamiento de idiomas padre';
$string['parentlangbehaviour_desc'] = '<p>Para decidir si debe
visualizar o no un bloque de idioma, el filtro trata de emparejar los
idiomas especificados en dicho bloque con el idioma actual utilizado
por el usuario que está visualizando el contenido. Este emparejamiento
se puede realizar de tres formas diferentes, a las que el filtro llama
<em>comportamiento de idiomas padre</em>:</p>
<ul>
<li><b>Usar idiomas padre siempre, excluído \'en\'</b>. <em>Este es el
comportamiento tradicional del plugin</em>, y es el valor
predeterminado para el ajuste. Cuando se selecciona este
comportamiento, el filtro usa tanto los idiomas especificados en el
bloque, como los idiomas padre de éstos (de forma recursiva hasta la
cima), para emparejar el idioma actual utilizado por el usuario que
está visualizando el contenido. Pero el idioma Inglés (\'en\') nunca
se considera un idioma padre en este caso, y se elimina de la lista de
idiomas padre. Por ejemplo, si un bloque de idioma especifica
\'en_kids\' en su lista de idiomas, entonces dicho bloque <em>no</em>
se mostrará si el idioma actual del usuario que visualiza el contenido
es \'en\'. Nótese que el filtro sí usará el idioma Inglés si éste se
especifica <em>explícitamente</em> en el bloque de idioma (p.ej.,
<small><tt>{mlang en}Este texto sí se mostrará si el idioma actual del
usuario que visualiza el contenido es
\'en\'{mlang}</tt></small>).</li>
<li><b>Usar idiomas padre siempre, incluído \'en\'</b>. Este
comportamiento funciona como "Usar idiomas padre siempre, excluído
\'en\'", pero no elimina el idioma Inglés (\'en\') de la lista de
idiomas padre. Así por ejemplo, si un bloque de idioma especifica
\'en_kids\' en su lista de idiomas, entonces dicho bloque <em>sí</em>
se mostrará si el idioma actual del usuario que visualiza el contenido
es \'en\'.</li>
<li><b>No usar idiomas padre nunca</b>. Como su nombre indica, este
comportamiento nunca usa los idiomas padre para emparejar el idioma
actual utilizado por el usuario que está visualizando el contenido. El
filtro se limita a usar únicamente los idiomas especificados en el
bloque de idioma.</li> </ul>';
$string['parentlangdefault'] = 'Usar idiomas padre siempre, excluído \'en\' (comportamiento tradicional).';
$string['parentlangalwaysen'] = 'Usar idiomas padre siempre, incluído \'en\'.';
$string['parentlangnever'] = 'No usar idiomas padre nunca.';
$string['pluginname'] = 'Filtro Contenido Multi-Idioma (v2)';
$string['privacy:metadata'] = 'El plugin del filtro de Contenido Multi-idioma (v2) no almacena ningún dato personal.';
Loading

0 comments on commit a462d85

Please sign in to comment.