Jump to content

Multi-language page names / URLs


ryan

Recommended Posts

Ryan, just wanted to say - thank you for this module, it will allow making multilanguage sites a breeze, I've used LanguageLocalizedURL module on some sites I've developed, but having it naturally in core, makes me wink ;)

  • Like 1
Link to comment
Share on other sites

Finally had a chance to test this new functionality and have been really impressed. For those wanting to try, here are the steps that I went though using the default site profile. In the following examples I am adding two languages English and German where the pages will be viewable with the language code preceding the url like /en/ and /de/ 

1.Install the latest development version - here is the zip download - https://github.com/ryancramerdesign/ProcessWire/archive/dev.zip

2. Install these 3 modules

- Languages Support

- Languages Support - Fields

- Languages Support - Page Names

3. Add a new language via the Languages setup page - I think naming here is only relevant for your code and does not affect the URL of the page

4. Open up the home page and look at Settings tab - the name (url) field should now have a field for each language you have added. Add an indicator for each language like /en and /de


English 

/en/

Deutsch 

/de/

This acts as a language gateway for changing the language. Now when ever you visit the site via /de it will set the users language to German and all the pages in the site will display the correct German language url.

note: I have not worked out how to redirect the default language (which is English) to /en - it always goes to the site root / 

4. Add a simple language navigation switcher. This will allow users to switch the current page into the other language.

Edit : Refer to WillyC and Ryans language switcher further down - http://processwire.com/talk/topic/2979-multi-language-page-names-urls/page-3#entry33537

                echo '<ul>';
                    $lang = $user->language;
                    $langname = $lang->name == 'default' ? 'en' : $lang->name;

                    $user->language = $languages->get('default');
                    $cssClass = $langname == 'en' ? 'class="active"' : '';
                    echo '<li '.$cssClass.'><a href="'.$page->url.'">EN</a></li>';

                    $user->language = $languages->get('de');
                    $cssClass = $langname == 'de' ? 'class="active"' : '';
                    echo '<li '.$cssClass.'><a href="'.$page->url.'">DE</a></li>';

                    $user->language = $lang;    
                echo '</ul>';

I think that is pretty much it. All other PW development procedes as normal.

I also just switched over from the LanguageLocalizedURL module on one site - it was actually pretty easy to do - just uninstalled the module and went through the steps above - thanks soma and mcmorry for the original module - it was very useful at the time, but will be moving over this mainly so I can start using the ProCache module. I have tested the new language fields with ProCache and it also works great! Super fast, one tree multilingual sites, with the same ease as developing a regular processwire site - this is so cool!

  • Like 8
Link to comment
Share on other sites

this another language.swatch

work wit any num.languagees

new localUrl func

echo "<ul>";
foreach($languages as $language) {
  $class="$language"=="$user->language" ? "active" : "";
  $url=$page->localUrl($language); 
  echo "<li class=\"$class\"><a href=\"$url\">$language->title</a></li>";
}
echo "</ul>"
  • Like 7
Link to comment
Share on other sites

Something to add to WillyC's example is checking whether the given page is active for the language. This is only necessary if you are selectively having some pages published in one language and not in another (meaning, you are using the "active" checkboxes to the right of the language-specific page names). 

echo "<ul>";
foreach($languages as $language) {
  if(!$page->viewable($language)) continue; // check if page not published for this language
  $class = "$language" == "$user->language" ? "active" : "";
  $url = $page->localUrl($language); 
  echo "<li class='$class'><a href='$url'>$language->title</a></li>";
}
echo "</ul>"; 

The only change I added above was the if(!$page->viewable($language)) continue; That makes it skip over languages that aren't published for $page. If that check weren't there, then the user would just see a 404 page when attempting to view in the unpublished language. 

  • Like 4
Link to comment
Share on other sites

@Henning: The Google documentation about creating sitemaps says:

A Sitemap file is independent of the language of the content. To make sure that each language version can be crawled and indexed, use unique URLs. These URLs can all be included in your Sitemap files.

So basically each domain has one sitemap.xml. You put all of the pages in it.

  • Like 1
Link to comment
Share on other sites

Hi,

where can I include a language switcher, which sets, if exists, the site language automatically to the browser language?
Maybe Redirection from /home/ to  /home/de/ or /home/en/
I am looking for something like this:

$user->language->name = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);

 

Thanks for help

kixe

Link to comment
Share on other sites

Your code looks ok to switch the language o the same page. Just make sure your language names conform to the php language codes. If you want to redirect to the language urls, you can do this:

$session->redirect($page->url . substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2));

edit: of course you have to have a default, because your website won't have a page for each language in the world.

Link to comment
Share on other sites

Hi,

first step is done. language switcher is working. I gave the / directory the names "en", "de" and "fr".
If I am now putting out $page->name on the site, I always get "en", even if I have switched to "de". Why?
What is the right api to get the names? I tried out with substr($page->url,1,2) which works. But is it really safe? Put out bool(false) in case of "/" (home directory in default language). It would be nice to have en API which put out language iso-code of the current page.

Ideas?
kixe

Link to comment
Share on other sites

Hi,

first step is done. language switcher is working. I gave the / directory the names "en", "de" and "fr".

If I am now putting out $page->name on the site, I always get "en", even if I have switched to "de". Why?

What is the right api to get the names? I tried out with substr($page->url,1,2) which works. But is it really safe? Put out bool(false) in case of "/" (home directory in default language). It would be nice to have en API which put out language iso-code of the current page.

Ideas?

kixe

If you have latest LanguagePageNames, and you have set the home/root page the names "en", "de" and "fr"...?

You can access the root name through the root page name.

$lang_segment = $pages->get("/")->name;

From anywhere in you templates no matter what sub page. But maybe with this setup isn't really save, if you don't have those names set or the default would have no segment name, it's not as reliable to assume it is set. But you could get around it.

Better you just get the current user language in the front-end, since this is automaticly set by the language names module. This will have the language name that is set in the language support. Since default name can't be changed you have to get it like this to set default language code.

$langCode = $user->language->isDefault() ? 'en' : $user->language->name;
Link to comment
Share on other sites

Thanks Soma I will try out this.

I implemented urlSegments to the switcher. Ryan, does it make sense and is it possible, to put this option in the function hookPageLocalUrl in the modul-file?

<?echo "<ul>";
foreach($languages as $language) {
    if(!$page->viewable($language)) continue; // check if page not published for this language
    $class = "$language" == "$user->language" ? "active" : "";
    $urlSeg1=($input->urlSegment1)?$input->urlSegment1.'/':'';
    $urlSeg2=($input->urlSegment2)?$input->urlSegment2.'/':'';
    $urlSeg3=($input->urlSegment3)?$input->urlSegment3.'/':'';
    $url = $page->localUrl($language).$urlSeg1.$urlSeg2.$urlSeg3;
    echo "<li class='$class'><a href='$url'>$language->title</a></li>";
}
echo "</ul>";
?>
 

greetings from Paris
kixe

Link to comment
Share on other sites

  • 4 weeks later...

I have a problem with $page->localUrl() throwing error at 404 page:

Fatal error: Exception: Method Page::localUrl does not exist or is not callable in this context (in /Users/Roope/Sites/demo/wire/core/Wire.php line 320) 
#0 /Users/Roope/Sites/demo/site/templates/inc/header.php(61): Wire->__call('localUrl', Array) 
#1 /Users/Roope/Sites/demo/site/templates/inc/header.php(61): Page->localUrl(Object(Language)) 
#2 /Users/Roope/Sites/demo/site/templates/basic.php(1): include('/Users/Roope/Si...') 
#3 /Users/Roope/Sites/demo/wire/core/TemplateFile.php(139): require('/Users/Roope/Si...') 
#4 [internal function]: TemplateFile->___render() 
#5 /Users/Roope/Sites/demo/wire/core/Wire.php(359): call_user_func_array(Array, Array) 
#6 /Users/Roope/Sites/demo/wire/core/Wire.php(317): Wire->runHooks('render', Array) 
#7 /Users/Roope/Sites/demo/wire/m in /Users/Roope/Sites/demo/index.php on line 214

In header.php around line 61 I have language switcher code borrowed from previous page:

<?php 
echo '<ul>';
	foreach($languages as $language) {
		if(!$page->viewable($language)) continue; // check if page not published for this language
		$class = "$language" == "$user->language" ? ' class="active"' : '';
		$url = $page->localUrl($language); 
		echo '<li'.$class.'><a href="'.$url.'">'.$language->title.'</a></li>';
	}
echo '</ul>';

I updated PW to latest dev but that didn't have any affect on this one. Besides 404 error, everything else runs smoothly. Any thoughts?

Link to comment
Share on other sites

Little addition to my previous post: when I throw 404 manually from page template, I get no errors and page renders and translates normally:

throw new Wire404Exception();

But everytime system does it, I receive error described above.

Link to comment
Share on other sites

I'm not sure I understand this case. The error you are getting is what I'd expect if the LanguageSupportPageNames module wasn't installed. But since it is installed, it's a bit of a mystery there. I'll see what I can do in trying to reproduce it. But you do have an alternative to the localUrl() function, and that is to just set the user's language for each iteration of your $languages loop, and then call $page->url:

$saved = $user->language; // save user's language
foreach($languages as $language) {
  if(!$page->viewable($language)) continue;
  $class = "$language" == "$saved" ? " class='active'" : '';
  $user->language = $language; 
  echo "<li$class><a href='$page->url'>$language->title</a></li>";
}
$user->language = $saved; // restore user's language
Link to comment
Share on other sites

Thank you Ryan!

This solves error message, but now 404 doesn't get localizated - text is always in default language when system throws it. Better than errors though!

I can send you site package and db if there's any help?

Link to comment
Share on other sites

Does the "Page Clone" module work with multi-language page names? 

I just installed page clone module - when trying to copy a page it just gets stuck at the "This will make a copy of…" screen. Clicking submit just reloads the page - no error messages in logs or console. and no pages are copied.

Using latest dev version.

Link to comment
Share on other sites

This solves error message, but now 404 doesn't get localizated - text is always in default language when system throws it. Better than errors though!

I'm not sure that a 404 page should be localized since it's responding to a URL that doesn't exist and thus has no language. Though you could potentially detect the language from your 404 page template by having your code look at the $_SERVER['REQUEST_URI'] for something that clarifies the language. Even better may be to have your code look at the $_SERVER['HTTP_REQUEST_LANGUAGE'] and set the $user->language consistent with that before rendering.

Link to comment
Share on other sites

I think you're right Ryan but maybe logic shoud be coherent all the times when 404 page is rendered.

Consider this kind of scenario (that's used in this particular project). You've translated homepage name to match your language setup so every page has language identifier prefixed in url:

domain.com/en/products/
domain.com/de/produkte/
domain.com/se/produkter/

Then if you enable url segments in this product template and handle false matches by throw new Wire404Exception():

domain.com/de/produkte/somefalsepath/ // here 404 page is in german when 404 is thrown manually from template
domain.com/de/somefalsepath/ // here 404 page is in default language when system throws it

It would be more logical if 404 page has same language in both cases.

  • Like 1
Link to comment
Share on other sites

Thanks for pointing out the discrepancy–now I understand what you are saying, it makes sense.  Technically, when you throw your own 404 in a template file, then the system has already determined the page and language, and the URL actually did resolve to something. But the template file decided it wasn't valid for one reason or another. I could have our 404 handler just set the language back to default. But the problem there is that this screws up the folks that are using subdomains to set the language (where a language-specific 404 could be legitimate). So I'm a little hesitant to change anything here. I think it might be best for the developer to decide what behavior they want from their 404 template file. But will have to think on this a little more to see if we can facilitiate anything better. 

  • Like 1
Link to comment
Share on other sites

I've pushed and update to the dev branch so that sorting should now work across languages. Previously, calls like these would still result in the returned pages being sorted by the default language field, regardless of the user's language: 

$pages->find("..., sort=name"); 
$pages->find("..., sort=title"); 
$pages->find("..., sort=pageref.name");
$pages->find("..., sort=pageref.title");
// and so on

To demonstrate the problem, lets say that you had these three pages:

  • /numbers/one/
  • /numbers/two/
  • /numbers/three/

In Spanish, those pages would be named:

  • /numeros/uno/
  • /numeros/dos/
  • /numeros/tres/ 

Lets say I executed this in English (my default language):

$pages->get('/numbers/')->children('sort=name');

The result would be alphabetical:

  • /numbers/one/
  • /numbers/three/
  • /numbers/two/ 

If I executed it in Spanish, the order would still be the same, sorted by the English spelling (which is clearly not correct):

  • /numeros/uno/
  • /numeros/tres/
  • /numeros/dos/

They should be sorted alphabetically by Spanish instead. With the latest commit to the dev branch, they should now sort correctly for the given language. Meaning the Spanish results would now be in this order: 

  • /numeros/dos/
  • /numeros/tres/
  • /numeros/uno/

The scope of those goes beyond page names. This also affects multi-language text fields. So if you've got a multi-language 'title' field for instance, you can sort by that, or any other multi-language field you are using, and the sorting should work properly now. It does not yet work with language-alternate fields, though I think it's probably unusual to use those fields as sorting keys anyways. 

  • Like 7
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...