Skip to content

docs(extending-modx/internationalization): fix the tag syntax and the lexicon coordinates - #676

Open
Ibochkarev wants to merge 4 commits into
modxorg:3.xfrom
Ibochkarev:docs/extending-modx-i18n
Open

Ibochkarev wants to merge 4 commits into
modxorg:3.xfrom
Ibochkarev:docs/extending-modx-i18n

Conversation

@Ibochkarev

Copy link
Copy Markdown
Collaborator

Follow-up to #662, from the review comments on that pull request.

Summary

All four remarks on en/extending-modx/internationalization.md are addressed. The Russian page follows.

1. The syntax of the tag

The example of a placeholder was written as

[[!%userinput=`amazing`]]

which is not a valid tag. The parser splits a tag on the first ? (core/src/Revolution/modParser.php:406), so without it the value stays part of the name — modLexiconTag would look for an entry called userinput=amazing``. The old example of the page had the same defect in the namespace property, where the name was written as namespace\_name: the backslash is not an escape in the syntax of a tag, so the namespace was looked up with the backslash in it.

The example is now

[[!%key? &userinput=`amazing`]]

2. The optional properties

The topic and the language can be left out — the topic falls back to default, the language to the one of the request. The namespace falls back to core as well, so it has to be given for a lexicon outside the core namespace; without it the tag looks in the core only (core/src/Revolution/modLexiconTag.php, getContent()).

3. The topics of the JavaScript connector

The page states that connectors/lang.js.php takes a comma-separated topic parameter. The basis is now named: the connector reads $_REQUEST['topic'], splits it by commas and loads every coordinate with modLexicon::load(). The templates of the core ask for the topics the same way — manager/templates/default/header.tpl requests ?ctx=mgr&topic=topmenu,file,resource,….

4. The lexicons of the settings

The label of a setting is the key setting_<key>, and its description is setting_<key>_desc. The core keeps its labels in the setting topic (core/lexicon/en/setting.inc.php, 540 entries), a Component outside the core namespace keeps them in the default topic — that is also the topic the manager writes a translated label into, because modSystemSetting::updateTranslation() picks the topic by the namespace of the setting (core/src/Revolution/modSystemSetting.php:41).

The Settings grid loads both topics of the namespace of the setting (core/src/Revolution/Processors/System/Settings/GetList.php:121-128), so a key with the setting_ prefix is found in either of them.

Also

The example of modLexicon::load() is rewritten: every argument is a coordinate, there is no separate parameter for the namespace ($topics = func_get_args()).

The description of the frontmatter of both locales is quoted, and the stray quote at its end is gone.

Checks

  • markdownlint-cli@0.18 (the version used by CI): 0 issues
  • EN/RU parity: the same headings, the same code blocks and the same tables
  • The links of both pages resolve

… lexicon coordinates

The review of modxorg#662 left four remarks on the page, all of them about
the code.

**The syntax of the tag.** The example of a placeholder was written as
`[[!%userinput=`amazing`]]`, which is not a valid tag: the parser splits
the tag on the first `?`, so without it the value stays part of the
name. The old example of the page had the same problem in the
namespace property, where the name was written as `namespace\_name` —
the backslash is not an escape in the syntax of a tag. The example is
now `[[!%key? &userinput=`amazing`]]`.

**The optional properties.** The `topic` and the `language` can be
left out — the topic falls back to `default`, the language to the one
of the request. The `namespace` falls back to `core` as well, so it has
to be given for a lexicon outside the core namespace; without it the
tag looks in the core only.

**The topics of the JavaScript connector.** The page states that
`connectors/lang.js.php` takes a comma-separated `topic` parameter.
The connector reads `$_REQUEST['topic']`, splits it by commas and
loads every coordinate with `modLexicon::load()`; the templates of the
core ask for the topics the same way
(`manager/templates/default/header.tpl` requests
`?ctx=mgr&topic=topmenu,file,resource,…`). The page now says so.

**The lexicons of the settings.** The label of a setting is the key
`setting_<key>`, and its description is `setting_<key>_desc`. The
core keeps its labels in the `setting` topic, a Component outside the
core namespace keeps them in the `default` topic — that is the topic
the manager writes a translated label into
(`modSystemSetting::updateTranslation()` picks the topic by the
namespace of the setting). The Settings grid loads both topics of the
namespace, so a key with the `setting_` prefix is found in either of
them.

The example of `modLexicon::load()` is rewritten as well: every
argument is a coordinate, there is no separate parameter for the
namespace.

The `description` of the frontmatter of both locales is quoted, and the
stray quote at its end is gone.
@Ibochkarev
Ibochkarev requested a review from Mark-H October 20, 2026 14:27
@Mark-H

Mark-H commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

Where does this concept of manually including /connectors/lang.js.php to load extra lexicon topics from? That's the biggest issue still present. That's how core works yes but not how people extending modx should be loading their lexicons.

… call the connector

The page told the reader to load extra lexicon topics in the browser by
calling `connectors/lang.js.php` with a `topic` parameter. That is how
the core does it, but it is not how a Component does it: the connector
is the manager's own mechanism, and the manager calls it for the topics
of the page.

A page declares the topics it needs and the manager loads them:

- a controller names them in `getLanguageTopics()`
  (`core/src/Revolution/modManagerController.php:533`, the core
  controllers override it);
- a page that needs a topic beyond those of its controller adds it with
  `addLexiconTopic()` (`modManagerController.php:1027`).

Both put the topic into `_lang_topics`, which the header template hands
over to the connector (`manager/templates/default/header.tpl:23`), and
the response replaces `MODx.lang`.

The Russian page follows.
@Ibochkarev

Copy link
Copy Markdown
Collaborator Author

You are right, and the page no longer tells the reader to call the connector.

Where the concept comes from. connectors/lang.js.php?topic=… is the manager's own mechanism: the header of the manager page asks the connector for the topics of the page (manager/templates/default/header.tpl:23 — ?ctx=mgr&topic=topmenu,file,resource,{$_lang_topics}), and the connector reads $_REQUEST['topic'], splits it by commas and loads every coordinate with modLexicon::load(). The response replaces MODx.lang.

How a Component does it. The page declares the topics and the manager loads them:

  • a controller names them in getLanguageTopics() — core/src/Revolution/modManagerController.php:533; the core controllers override it (controllers/default/context/index.class.php:70 returns ['context']);
  • a page that needs a topic beyond those of its controller adds it with addLexiconTopic() — modManagerController.php:1027.

Both load the topic and put it into _lang_topics, which the header hands over to the connector. The order works out: prepareLanguage() runs at the start of render(), and process() runs before the header is rendered (modManagerController.php:162,180,203).

The page now says that a Component does not call the connector itself — the manager does. The Russian page follows.

@Jako Jako left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added a comment about loading the lexicons with the lang.js.php

Comment thread en/extending-modx/internationalization.md Outdated
…ts own topics

The page told that a controller names its topics in `getLanguageTopics()`,
but did not say that this is the way a Component adds the topics of its
own namespace, and it did not say why the connector is not the way to
add topics while the page runs.

It is now stated in both places:

- to add the topics of an own component, name them in
  `getLanguageTopics()` of a controller of the component — it extends
  the list of the lexicons loaded for the page;
- the connector does not extend the lexicon of a page that is already
  running. It answers with the topics of its own request — `core:default`
  and the ones asked for — and the response replaces `MODx.lang`, so the
  topics have to be declared before the page is rendered.

The Russian page follows.
```

A page declares the topics it needs, and the manager loads them into `MODx.lang`. A controller names them in `getLanguageTopics()`:
A page declares the topics it needs, and the manager loads them into `MODx.lang`. To add the topics of an own component, name them in `getLanguageTopics()` of a controller of the component — it extends the list of the lexicons loaded for the page:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To add multiple lexicons, the parameter of getLanguageTopics can contain an array of topics instead of a string.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — the example returns a list now, and the page says that the method returns the list of the topics of the page:

public function getLanguageTopics()
{
    return ['mycomponent:default', 'mycomponent:settings'];
}

The code behind it: modManagerController::prepareLanguage() takes the return value of getLanguageTopics(), loads every topic of the list with modLexicon->load() and hands the list over to the header of the page through _lang_topics. The core controllers use the list the same way — for example return ['user','access','policy','context','setting']; in the controller of a user group.

One detail worth knowing, since the wording can be read both ways: every element of the list has to be a string with one topic. A nested array would not work — modLexicon::load() skips everything that is not a string, and implode(',', $languageTopics) would flatten it with a notice.

The Russian page follows.

…s() takes a list

The method returns the list of the topics of the page, so a component
names all of its topics at once. The example now returns two topics and
the page says so.

The code behind it: `modManagerController::prepareLanguage()` takes the
return value of `getLanguageTopics()`, loads every topic of the list
with `modLexicon::load()` and hands the list over to the header of the
page through `_lang_topics`. The core controllers use the list the same
way — `return ['user','access','policy','context','setting'];`.

The Russian page follows.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants