Polylang 3.4 eases the translation of custom tables

Polylang 3.4 will soon be released and it comes with some technical changes. This new version is more focused on developers and we will discuss these changes here.

In this release we wanted to:

  • Improve compatibility with php 8.2.
  • Improve reliability of some core components,
  • Prepare for more changes to come,
  • Ease extensibility with third party plugins.

In fact, the changes in this release are the first step towards more improvements to come in the plugin’s architecture, but that’s a subject to discuss another day 😏.

PLL_Language

This is the class holding the data related to a language, and is the starting point of our improvements. Until now we had an object with properties set as soon as the instance is created, some were set later, and some other were generated dynamically on-the-fly. This was a bit difficult to work with properties that were maybe available, or maybe not. So we changed a few things to get a more “reliable” tool.

So what does it mean for you?

term_taxonomy_id, count, tl_term_id, tl_term_taxonomy_id, and tl_count. Note that term_id hasn’t changed.

Those properties are still available publicly but will trigger a deprecation warning when accessed directly. To retrieve these values, a new getter is available:

PLL_Language::get_tax_prop( $taxonomy, $field )

Where $taxonomy is a taxonomy name like 'language' and 'term_language', and $field is the name of the field to retrieve: 'term_id', 'term_taxonomy_id', or 'count' (those are the only 3 possible values).

Which gives us:

$language->term_taxonomy_id $language->get_tax_prop( 'language', 'term_taxonomy_id' )
$language->count $language->get_tax_prop( 'language', 'count' )
$language->tl_term_id $language->get_tax_prop( 'term_language', 'term_id' )
$language->tl_term_taxonomy_id $language->get_tax_prop( 'term_language', 'term_taxonomy_id' )
$language->tl_count $language->get_tax_prop( 'term_language', 'count' )

There is also another getter to retrieve values for all language taxonomies:

PLL_Language::get_tax_props( $field )

Example:

$ids = $language->get_tax_props( 'term_id' );
/**
 * [
 *     'language'      => 8,
 *     'term_language' => 10,
 * ]
 */

The $field argument can be omitted to retrieve all the fields.

home_url and search_url.

Like the previous ones, those properties are still available publicly but will trigger a deprecation warning when accessed directly. To retrieve these values, 2 new getters are available:

PLL_Language::get_home_url()
PLL_Language::get_search_url()

Some public properties are removed

$mo_id: it was used to store the ID of the post containing the strings translations (they are stored elsewhere now).

Some public methods are removed

The following methods are removed without deprecation warnings:

PLL_Language::set_flag()
PLL_Language::set_home_url()
PLL_Language::set_url_scheme()

They were used internally to set properties that are now set during the class instantiation.

New public properties

$is_default: handy to know if a language is the default one.

$active: tells whether or not the language is active. The default value is true. Note that deactivating languages is still a feature of Polylang Pro, this property is not used in Polylang.

$fallbacks: list of fallback language locales. The default value is an empty array. Note that this also a feature of Polylang Pro, this property is not used in Polylang.

API functions

But let’s not forget that API functions are available:

pll_current_language()
pll_default_language()
pll_get_post_language()
pll_get_term_language()

These functions have a $field = 'slug' parameter and work like before. They can retrieve a field value without triggering a deprecation warning. However 'tl_term_id', 'tl_term_taxonomy_id', and 'tl_count' should not be used. Instead, the parameter accepts a new notation: '{language_taxonomy_name}:{property_name}'.

Example:

$tl_tt_id = pll_current_language( 'term_language:term_taxonomy_id' );

The languages list

A lot of things happened here. To put it simply, the list is considered incomplete if we try to retrieve it (with pll_languages_list()) before the hook pll_pre_init. If this function is called before this hook, Polylang will trigger a “doing it wrong” warning.

On pll_pre_init, the languages are considered ready and the list is cached when called the first time.

PLL_Model

Some hooks are deprecated

With all the changes, the filters pll_after_languages_cache and pll_languages_list are not needed anymore, and are deprecated.

Note: if you used pll_after_languages_cache to filter URLs, you may hook site_url instead.

Get a language by its term_taxonomy_id

Not the big feature, but a small improvement that can be handy sometimes: PLL_Model::get_language() can use a term_taxonomy_id to retrieve a language.

However, there is a trick. This method can also use a term_id, so to prevent a confusion between these two (they can be different), the value of the term_taxonomy_id must be prefixed with tt:. Example:

$lang = PLL()->model->get_language( 7 ); // term_id
$lang = PLL()->model->get_language( 'tt:8' ); // term_taxonomy_id

Translate custom database tables is now easier

You didn’t know this was already possible? Well, it was, but a bit of a chaotic road because nothing was really prepared for it.

Since its version 0.1, Polylang is focused on translating posts and terms. Everything was built on this idea and these two types of content became the only ones to be translated (kind-of).

While working on PLL_Language and PLL_Model, we also had to work on PLL_Translated_Post, PLL_Translated_Term, and their parent classes. We had to work on their common behaviors and differences, and ended up working on generic ways to orchestrate all this.

So, how do we do now?

First, you need to create a class that will extend either PLL_Translatable_Object or PLL_Translated_Object. These classes give you tools to easily get and set a language to an item, manage translations, etc.

  • PLL_Translatable_Object is meant for items that need a language assigned to, but don’t necessary need translations (meaning, a relation with other items in another language).
  • PLL_Translated_Object is probably what you’re looking for. It is meant for items that need a language and translations (PLL_Translated_Post and PLL_Translated_Term extend this class).

Another thing you may need here is the interface PLL_Translatable_Object_With_Types_Interface and the trait PLL_Translatable_Object_With_Types_Trait. You must use them if you want to manage types of your items. Example: post types for PLL_Translated_Post, and taxonomies for PLL_Translated_Term. By using them you can tell Polylang that some of your item types are translatable, and some are not.

Now that your class is ready, what to do with it? The answer is: register it in Polylang.

Let’s say your class is FooTranslatedItems:

add_action( 'pll_model_init', 'foo_register_in_polylang' );
/**
 * Registers the database table in Polylang.
 *
 * @return void
 */
function foo_register_into_polylang( $model ) {
    $translatedItems = new FooTranslatedItems( $model );
    $model->translatable_objects->register( $translatedItems );
);

And that’s it.

We created a new hook pll_model_init for this purpose and it is triggered right before pll_pre_init, so, right before the languages are considered complete.

Why registering a class?

By registering your class in Polylang you tell Polylang that your custom table exists and its content is translatable. Now Polylang will:

  • Automatically create the terms for your language taxonomy if they don’t exist yet.
  • Include your items when looking for untranslated objects (you know the admin notice in Polylang’ settings saying that some of the content is not translated yet) and allow the users to assign a language in bulk to all these items. This also works in Polylang’s wizard.
  • Update the translations when a language slug has been modified in the settings or delete them when a language is removed.

Oh, and you will find your language term_id, term_taxonomy_id, and count in PLL_Language::get_tax_prop().

Still confused?

OK, this may still be a bit confusing without seeing code, so we created a small example plugin for you to explore.

Your main focus should be on:

  • polylang-translated-table-example.php: it will show you how to register your class.
  • src/TranslatedEvents.php: it will show you how to extend and use PLL_Translated_Object.

Polylang won’t do everything automatically simply by creating this class and registering it, it is a process that has many aspects that can’t be done on our side, but it gives you a good starting point to make your custom table translatable in Polylang.

Conclusion

Polylang is currently putting some efforts into code improvements and we expect to continue in the following major versions. Some parts must be cut down at some point, but of course we try to keep backward compatibility as much as possible.

You can test the beta version of Polylang 3.4 as of today. The beta version of Polylang Pro can be downloaded from your account. You can also download the beta version of Polylang for WooCommerce 1.8 there. It is necessary to avoid deprecation warnings with Polylang 3.4 . Bug reports are welcome on GitHub.

Picture illustrating the article by Lorenzo Cafaro and licensed under the Pixabay license.