Skip Navigation
Updated
July 17, 2024

If you created a plugin that stores content in custom database tables, you can make this content translatable by WPML. This includes forms, visual editors, events, and anything that doesn’t fit into the standard WordPress post, post meta, or wp_options tables. 

How Translation of Content in Custom Database Tables Works

If your plugin stores data in custom database tables, WPML has no way of knowing how to access this data or how to return it once translated. 

To let you easily register this data for translation, WPML’s String Translation plugin comes with a functionality that lets you neatly organize texts in custom database tables into “text groups,” and bundle these groups within a specific “context”. This setup significantly streamlines the translation process for end-users. 

For example, let’s say your plugin allows users to create front-end WordPress forms. The forms can include various elements such as input fields, select dropdowns, and a textarea. Each of these elements is an individual piece, but together, they create the form. In this scenario, the collection of form fields makes the “text group.” The form itself, the broader environment where these fields are used, represents the “context.”

By keeping form fields bundled together in this way, you make it easier for translators to understand the relationship between elements. This leads to faster and more accurate translations.

Our Example Case

Throughout this guide, we’ll use a recipe plugin that stores data, such as ingredients and preparation steps, in custom tables, as an example. 

This will illustrate the steps for registering strings for translation, informing WPML about the final strings and removing unused strings, and ensuring that translated strings display on the front-end.

Default language page with ingredients and steps coming from a recipe plugin

Translated page with ingredients and steps coming from a recipe plugin

Registering the Strings for Translation

To start, you need to register the strings for translation. For this, WPML provides the wpml_register_string action. 

Here’s what you need to add to your code when you create or update texts:

do_action( 'wpml_register_string', string $string_value, string $string_name, array $package, string $string_title, string string_type );

Arguments:

  • $string_value: (string) (Required) –  The string you want to translate.
  • $string_name: (string) (Required) –  A unique identifier for the string within the package.
  • $package: (array) (Required) – This groups related strings together, so they are managed as part of the same logical unit or “package” for translation. It ensures a structured and organized way to handle translations within the same context.
  • $string_title – (string) (Required) The title of the string to be displayed when editing the translation.
  • $string_type – (string) (Required) The edit control type for the string. ‘LINE’, ‘AREA’, ‘VISUAL’

For example, here’s an array detailing the context, including kind, name, title, and links for editing and viewing a recipe:

$package = array(
   'kind' => 'My Awesome Recipe',
   'name' => 'spaghetti-carbonara',
   'title' => 'Spaghetti Carbonara',
   'edit_link' => 'LINK TO EDIT THE RECIPE',
   'view_link' => 'LINK TO VIEW THE RECIPE'
);

Content of the $package array:

  • $kind – The object type, for example, “Gravity Form” or “Layout”. This is used in the Translation Dashboard to filter objects for translation.
  • $name – A unique identifier for the object that remains unchanged over time. For posts, the post ID is often used.
  • $title – The display name for the object, shown in the Translation Dashboard.
  • $edit_link – A URL to edit the object, shown in the Translation Management dashboard. You can omit this or set it to an empty string.
  • $view_link – A URL to view the object, shown in the Translation Management dashboard. You can omit this or set it to an empty string.

Here’s how our custom recipe content will appear on the WPML Translation Management page:

How the content of the $package array displays in Translation Management

Practical Implementation

Let’s say you have an action that runs whenever a user updates or creates a recipe. This action allows other components of the WordPress installation to perform additional tasks. Here’s how you might set it up:

do_action( ‘my_awesome_recipe_saved’, ‘spaghetti-carbonara’ );

Here, 'spaghetti-carbonara' serves as the unique identifier for the recipe.

Now, suppose your plugin includes a function that retrieves the recipe’s data:

$recipe = my_awesome_recipe_get_recipe( $name );

With this setup, you can register the strings of the recipe for translation using the wpml_register_string action. Here’s how you can do it for both the ingredients and the preparation steps of the recipe:

function register_recipe_strings( $recipe ) {
   foreach ( $recipe->ingredients as $index => $ingredient ) {
      $string_value = $ingredient;
      $string_name  = 'ingredient-' . $index;
      $string_title = 'Ingredient ' . $index;
      $package      = [
         'kind' => 'My Awesome Recipe',
         'name' => $recipe->name,
      ];
      do_action( 'wpml_register_string', $string_value, $string_name, $package, $string_title, 'LINE' );
   }


   foreach ( $recipe->steps as $index => $step ) {
      $string_value = $step;
      $string_name  = 'step-' . $index;
      $string_title = 'Step ' . $index;
      $package      = [
         'kind' => 'My Awesome Recipe',
         'name' => $recipe->name,
      ];
      do_action( 'wpml_register_string', $string_value, $string_name, $package, $string_title, 'AREA' );
   }
};


add_action( 'my_awesome_recipe_saved', function( $name ) {
   $recipe = my_awesome_recipe_get_recipe( $name );


   register_recipe_strings( $recipe );
} );

This approach effectively creates a string package for the recipe, making it ready for translation. 

By going to WPML → Packages on your WordPress website, you can see the string package and details like the number of strings it contains and the default language of these strings.

An example of a string package created for a recipe, as listed on the Package Management page

Translating the String Package

Once your custom content is registered for translation with WPML, your plugin’s users can easily send it for translation.

To translate a string package, go to WPML → Translation Management page:

  1. From the top bar, use the first All types dropdown menu to filter for your string package.
  2. Select the string package you want to translate.
  3. Choose whether you want to translate the string package automatically or by yourself and click Start Translating.
Sending the package for translation via Translation Management

If you selected the option to translate yourself, go to WPML → Translations and take the translation job. This opens WPML’s Advanced Translation Editor, where you can enter the translations for each string in the package.

Translating the fields coming from the package in the Advanced Translation Editor

Updating Strings and Removing Unused Ones

Sometimes, users may update content within your plugin. For example, a user may update a recipe to add, modify, or delete ingredients or preparation steps.

In this case, WPML needs to be able to identify the current strings and remove those that are no longer needed. This process involves wrapping the registration code between two WPML actions: wpml_start_string_package_registration and wpml_delete_unused_package_strings.

These actions mark the start and end of the registration process, ensuring WPML keeps only relevant strings. 

Here’s an example of what the updated code looks like:

function my_awesome_recipe_get_package( $recipe ) {
   return [
      'kind'  => 'My Awesome Recipe',
      'name'  => $recipe->name,
      'title' => $recipe->title,
   ];
}
function register_recipe_strings( $recipe ) {
   foreach ( $recipe->ingredients as $index => $ingredient ) {
      $string_value = $ingredient;
      $string_name  = 'ingredient-' . $index;
      $string_title = 'Ingredient ' . $index;


      do_action( 'wpml_register_string', $string_value, $string_name, my_awesome_recipe_get_package( $recipe ), $string_title, 'LINE' );
   }


   foreach ( $recipe->steps as $index => $step ) {
      $string_value = $step;
      $string_name  = 'step-' . $index;
      $string_title = 'Step ' . $index;


      do_action( 'wpml_register_string', $string_value, $string_name, my_awesome_recipe_get_package( $recipe ), $string_title, 'AREA' );
   }
};


add_action( 'my_awesome_recipe_saved', function( $name ) {
   $recipe = my_awesome_recipe_get_recipe( $name );


   do_action( 'wpml_start_string_package_registration', my_awesome_recipe_get_package );


   register_recipe_strings( $recipe );


   do_action( 'wpml_delete_unused_package_strings', my_awesome_recipe_get_package );
} );

Displaying Translated Content on the Front-End

Before displaying the data on the front-end, your theme or plugin needs to filter the data through a filter hook. Here’s an example of what this could look like for our recipe plugin:

$recipe = apply_filters( 'my_awesome_recipe_get_recipe', my_awesome_recipe_get_recipe( $name ) );

Now, to ensure each string within the content is translated, you need to use the wpml_translate_string filter. Continuing with our example, this approach makes it possible to translate each component of the recipe individually:

add_fitler( 'my_awesome_recipe_get_recipe', function( $recipe ) {
   foreach ( $recipe->ingredients as $index => $ingredient ) {
      $string_value = $ingredient;
      $string_name  = 'ingredient-' . $index;


      $recipe->ingredients[ $index ] = apply_filters( 'wpml_translate_string', $string_value, $string_name, my_awesome_recipe_get_package( $recipe ) );
   }


   foreach ( $recipe->steps as $index => $step ) {
      $string_value = $step;
      $string_name  = 'ingredient-' . $index;


      $recipe->steps[ $index ] = apply_filters( 'wpml_translate_string', $string_value, $string_name, my_awesome_recipe_get_package( $recipe ) );
   }


   return $recipe;
} );

Deleting a String Package

Whenever a user removes content, such as a recipe, from a WordPress site, it’s crucial to also delete the associated string package, along with all related strings, translations, and jobs. This allows the site to stay clean and efficient, without leftover data cluttering the database.

To achieve this, hook into an appropriate action within your plugin or theme and call the wpml_delete_package action provided by WPML. This action requires two parameters:

  • $name: The unique identifier of the package to be deleted.
  • $kind: The type of package being deleted.

The code snippet below shows the process using a hypothetical ‘my_awesome_plugin_before_delete’ action, which would be specific to your plugin or theme.

add_action( 'my_awesome_recipe_before_delete', function( $recipe ) {
   $package = my_awesome_recipe_get_package( $recipe );


   do_action( 'wpml_delete_package', $package['name'], $package['kind'] );
} 

This ensures that all translation data related to the content is properly removed alongside the content itself.