How to get the slug url using the LangSwitcher plugin?

I’ve seen that’s several people are having that same issue using LangSwitcher. I can’t get the slug url of my other language with the plugin. Even if I set the

redirect_default_route: true

Is there an other way to get the slug url of the current page of another language ?
Thank you very much.

@michael, @reboom If my issues were the same as yours, I might have a solution to point you in the right direction…

Grav has a function Page::translatedLanguages() (see docs on Page) which is said to do the following:

public translatedLanguages(bool $onlyPublished=false ): array the page translated languages
Return an array with the routes of other translated languages

Unfortunately this doesn’t work as I expected (issue #2163). After a “fix” it only returns nulls for each language…

I have therefor created my own LanguageSwitcher plugin with a rewritten translatedLanguages() method. The method returns an array with the proper slug for each supported (and translated) language.

Based on this array, the plugin can then:

  • Create proper urls to each alternate language of the current page.
  • Create correct absolute alternate links <link rel=“alternate”> for current language and alternate languages.

To test if this solves your issue you could try the following:

  1. Create fresh Grav installation
  2. Add the following to user/config/system.yaml:
      supported: [en, de, fr]
      include_default_lang: false  # Don't show language in url for default language
  3. Create translated pages for user/pages/01.home and user/pages/02.typography with correct titles and slugs.
  4. To create an unstyled “language switcher” using a <ul> containing urls for each translation, add the following somewhere in the <body> of the template user/themes/quark/templates/partials/base.html.twig:
    {% include 'partials/language-switcher-list.html.twig' %}
  5. To create <link rel=“alternate”> for each language, add the following to the <head> of user/themes/quark/templates/partials/base.html.twig:
    {% include 'partials/language-switcher-hreflang.html.twig' %}
  6. Lastly, download a ZIP of the repo grav-plugin-language-switcher and drop folder “grav-plugin-language-switcher-master” from inside the zip into user/plugins/ and rename the folder to “language-switcher”.

The <link rel=“alternate”> in the <head> of the page “Typography” should look like:

<link rel="alternate" hreflang="en" href="http://localhost/grav/site-test/typography" />
<link rel="alternate" hreflang="de" href="http://localhost/grav/site-test/de/typografie" />
<link rel="alternate" hreflang="fr" href="http://localhost/grav/site-test/fr/typographie" />

The <ul> HTML code containing urls to alternate langauges for the english (default) page “Typography” should look like":

    <a href="/grav/site-test/de/typografie">
      <img class="flag" src="/grav/site-test/user/plugins/language-switcher/images/de.png" alt="de">
      <span class="language">Deutsch</span>
    <a href="/grav/site-test/fr/typographie">
      <img class="flag" src="/grav/site-test/user/plugins/language-switcher/images/fr.png" alt="fr">
      <span class="language">Français</span>

If you don’t want to show the flags you can alter a copy of /user/plugins/language-switcher/language-switcher.yaml in /user/config.

As Andy said in issue #2026, this method will pull all translated *.md files from disk during processing of the current page. Since I’m only using the plugin for a site with 2 languages, I noticed no performance degradation. If many languages are involved and performance might become an issue, the results for each page could be cached on first request and retrieved from cache in subsequent requests.

The plugin is not very well documented and might be hard to grasp… Sorry for that, it was only intended for personal use.

Nonetheless, I hope this helps a bit.

Hi @pamtbaau,

thanks for your reply. Yes, this works fine even for slugs used in the path. Other solutions focus on the slug for the last part of the URL and ignore the slugs for the path itself (relying on Grav’s redirect feature for the then used raw route - which is really bad).

I didn’t do tests on how this performs. But one thing is that there’s a lot of page loading in the background. Assume you’re on a page on the 5th level and support 10 languages. Then there will be 50 pages loaded in the background just to retrieve the slugged paths. Ok, that’s a situation most sites won’t be in but one has to think of it.

My solution relys on the pages object which has the full tree built on each page request. Via getList() I retrieve the slugged paths for all pages of that language at once and cache them. So once all languages have at least one page request for each language the cache is fully set up with the slugged paths. But each change for a page makes a cache rebuilt necessary. Ok, I can improve this by setting the cache dirty only if the slugs changed. For now I set it dirty already even if just the content changed, since I use the pages’ cache id which changes then.

Your (good!) solution provides slugged paths independent of changes. No cache rebuilt is necessary. But each page request leads to a more or less page loading, depending on how deep the requested page is located in the structure and depending on how many languages are supported (simply multiply the numbers).
My (good, too!) solution provides slugged paths on a cache basis. I expect it to be much faster if structure is “deep” and number of languages is “high”. Backdraw: The cache needs to be rebuilt if a slug changes and at least one page request per language is necessary to build the cache (really just one for each language, not all pages of a language need to be requested since the pages object does this already in the background on each page request, building the whole page tree). But this can be done manually each time a change was made (which is what I do) or one can hook in an appropriate event each time a page was saved. Or one can perform simple requests for each language in the background once in a while.

I my project I need fastest response so I prefer my solution since the levels are deep and the number of languages will increase. But I will improve my solution using a better cache-dirty-indicator which leads to a decreased number of cache dirty events.

Happy slugging!

I modified my solution a little bit (improved caching/performance if slugs didn’t change when pages where modified plus renaming some variables). Thanks @pamtbaau for the inspiration to refactor my solution.

I updated my solution on github to have it in one place.

@reboom You are absolutely right that for each node in the path, I fetch all available languages. That might indeed become quite an overhead with (deep) nested pages combined with many languages. Wise caching might resolve some of that.

My solution was build for my specific needs: No nested pages and only 2 languages (native + English).

Just wondering, does you solution mean that slugs in the language-switcher might not be correct as long as a page/language has not been fetched yet?

@pamtbaau My solution was built for my specific needs, too :wink:

You’re right, as long as at least one request for a language has been handled (and no changes have been made) the slugs are correct. If after a change has been made to the slugs the cache (if one exists already) does not reflect the true situation. That’s not the best solution, I know. But if the site is at least semi professional you will prebuild the cache anyway. If the cache does not exist, then my plugin will use the Grav mechanism (raw routes + redirect). But precaching will help on this as well.

@pamtbaau I improved my solution again. Now the cache will be updated instantly on saving an altered (or newly created) page. Of course this is only possible if one is using the built in editor. The main drawback of outdated slugged routes is now minimized.

But altering a page outside Grav (which is what I do) will still not be recognized (but due to performance reasons my workflow implies precaching from outside grav each time a change was made).

For changes made see my post on Github (why is linking to Github not allowed?) A few days ago it worked fine.

Thanks to the community flagging my post to become irrelevant. I did a good work to have language specific slugged routes on high performance. And it’s a good explanation to the question why under normal conditions such slugged routes are not available and this should be irrelevant?

What’s going on? Helping others to solve their problems is not wanted?