Tuesday, June 9, 2015

Polylang: Identical page names in different languages

The problem: The links in a menu on a translated WordPress page should point to the translated destination.  PolyLang accomplishes this by adding a language specifier to the URL.  In our case, the URL uses directory names to specify stuff (like the language), so, for example, adding /es after the domain name would provide the Spanish version of a page.  The problem was that our menu items would add the "/es" twice.  This seemed to be a result of using both PolyLang and Enhanced Custom Permalinks.

WP appends a dash and a number (-#) to any slug that is already in use by a page in the same "sub" so that when it is used to build a permalink, it is different for each page in that "sub".  I put sub in quotes because whatever appears to be a subfolder in the URL helps make the URL distinct, but the language specifier doesn't count as one.  WP code wasn't written with the foresight that the same page might get multiple (distinct) URLs from some plugin (like PolyLang), so it enforces the rule that every page in the same "sub" (or in the root) must have a unique slug - even if there's already a unique language specifier in the URL.

But what if you want to preserve old URLs that would require the same slug?  For example, if you had different languages specified by having a language identifier in the URL, you won't want to add the -# (and you don't need to - because there is a language identifier already in the URL).

A client of mine ran into this problem which I've worked on solving using three methods.

The first method was an edit to the PolyLang file called links-directory.php so that the last line of add_language_to_link in links-directory.php massages the incoming $url.  Note that PolyLang code uses "$slug" to mean the language identifier.  My change replaces any existing occurrence of "/$slug" or "$slug/" with just a slash:
return str_replace($this->home . '/' . $this->root, $this->home . '/' . $this->root . $slug, str_replace(array($slug.'/','/'.$slug),'/',$url) );

 This seems to have fixed some of the problems but the menu items still had "/es/es" in them.

The second thing I did was to add some code from a post at Wordpress.org of the same name as this blog entry.  This didn't seem to have any effect, but I left it in place.

The last thing I did was because I noticed that one item in the menu didn't have this problem and I wondered why.  The reason was that the slug for the default language was misspelled (acapluco instead of acapulco).  Since it didn't match the slug for the Spanish version (correctly spelled, acapulco), the default permalink for that version contained the correctly-spelled slug and no -# (unlike all the other pages which did have -#).  The permalink for the default language had been manually corrected, so it was non-default.

When the permalink matches the slug, it's grey to indicate that it's the default permalink on the page-edit page (as if disabled, but you can still click it to change it).  Also when they match, the permalink code doesn't bother altering the URL and therefore doesn't trigger PolyLang to alter it (again) by adding a second "/es" which would otherwise break the URL.

Since the English version did have a non-default permalink, the code did massage the URL to add the language specifier (twice, I imagine), but since it was the default language, the specifier was the empty string and you can add that all you want without changing anything.  So the default language version can use any slug and a manually entered permalink and the changes that Enhanced Custom Permalink and PolyLang make to it don't matter.

That's pretty complicated, so here is the fix in simple steps that you have to perform on each page:
  1. Edit the default language version of the page.
  2. Change the slug to something you don't want to use (for example, change "contactus" to "contacus").
  3. Change the permalink so that it uses the slug you DO want to use (when you change the slug to "contacus" it will use that misspelling in the default permalink, so fix it back to "contactus").
  4. Click the pencil by each (non-default) language to edit those versions.
  5. Change the slug to what you DO want to use. (I think this will only work if you have ONLY one non-default language because when you change the slug for the second non-default language, it's going to match the first non-default language, so WP will add the -#.  I think)
  6. Change the permalink so that it matches the slug (and make sure you add the trailing slash).
  7. After clicking update, verify that the permalink is grey (indicating that it matches the slug).
At this point, all your internal links should point to the translated URL for the destination (with the /es or /ru or whatever) in it.