Skip Navigation
Updated
November 7, 2024

Learn how to use custom PHP code and WPML filters to manually register custom Elementor widgets for translation.

There are two types of custom Elementor widgets: 

To make your complex widget translatable with WPML, you can use one of two ways:

  1. Recommended: Using wpml-config.xml, WPML’s configuration file. The process is outlined in our tutorial on how to register page builder widgets for translation
  2. Manual: Using custom PHP code. If you want to use this method, you can follow the guidelines on this page.

Adding WPML Translation Support with PHP

To demonstrate the manual process, we took one of Elementor’s widgets and extended it to include WPML support. You can find the widget on this GitHub page.

1. Add a WPML Filter To Your Widget

For every custom widget plugin, you should add the wpml_elementor_widgets_to_translate filter during their init action. WPML uses this filter to gather more information about the widget texts that need translation.

WPML filter for Elementor widgets
add_filter( 'wpml_elementor_widgets_to_translate', [ $this, 'wpml_widgets_to_translate_filter' ] );

This filter passes the array that lists all the widget types that need to be translated. This array includes default Elementor parts and information about what needs translating.

You should add any new widget types to this structure, as in the following example.

Adding Elementor widgets
public function wpml_widgets_to_translate_filter( $widgets ) {
  $widgets[ $this->get_name() ] = [
     'conditions' => [ 'widgetType' => $this->get_name() ],
     'fields'     => [
        [
           'field'       => 'title',
           'type'        => __( 'Hello World Title', 'hello-world' ),
           'editor_type' => 'LINE'
        ],
     ],
  ];

  return $widgets;
}

2. Create a Custom Class That Will Handle Translations

With the filter in place, create a custom class to manage your widget’s translations. Specify the class name in the integration-class field.

Specifying a custom class that features more complicated widgets
public function wpml_widgets_to_translate_filter( $widgets ) {
  $widgets[ $this->get_name() ] = [
     'conditions' => [ 'widgetType' => $this->get_name() ],
     'fields'     => [],
      'integration-class' => 'My_Custom_Widget_WPML_Support’,
     ],
  ];

  return $widgets;
}

In the fields array, you can specify the following elements:

  1. field – The id of the field. This is the same as the id used when adding a control via the Control_stack::add_control function.
  2. type – The type of field. This is the text displayed in the WPML Translation Editor to help the translator know what field is to be translated.
  3. editor_type – This is the type of text field used in the WPML translation editor. Valid values are LINE, AREA, and VISUAL.

3. Implement the IWPML_Page_Builders_Module Interface

The integration class needs to implement the get and update functions. The get function returns the list of strings that need translation and the update function updates the Elementor data when a translation is received.

Example of a custom class that features more complicated widgets
/**
* Class IWPML_Page_Builders_Module
*/
interface IWPML_Page_Builders_Module {
  /**
   * @param string|int $node_id
   * @param mixed $element
   * @param WPML_PB_String[] $strings
   *
   * @return WPML_PB_String[]
   */
  public function get( $node_id, $element, $strings );

  /**
   * @param string|int $node_id
   * @param mixed $element
   * @param WPML_PB_String $string
   *
   * @return array
   */
  public function update( $node_id, $element, WPML_PB_String $string );
}

4. Handle Multiple Occurrences Of The Same Field In a Block Using field_id

If you have multiple occurrences of the same field inside a block, use the field_id to describe a field inside an Elementor block. This allows you to differentiate occurrences of the same field in a node.

Multiple occurrences of the same field
public function wpml_widgets_to_translate_filter( $widgets ) {
 $widgets[ $this->get_name() ] = [
    'conditions' => [ 'widgetType' => $this->get_name() ],
    'fields'     => [
       'title_1' => [
          'field'       => 'title',
          'field_id'   => 'title_1',
          'type'        => __( 'Hello World Title', 'hello-world' ),
          'editor_type' => 'LINE'
       ],
       'title_2' => [
          'field'       => 'title',
          'field_id'   => 'title_2',
          'type'        => __( 'Hello World Title', 'hello-world' ),
          'editor_type' => 'LINE'
       ],
    ],
 ];

 return $widgets;
}

5. Handle Widgets With Lists of Other Elements Using The WPML_Elementor_Module_With_Items Class

Some Elementor widgets, like the Slides widget, contain lists of elements (e.g., multiple slides with headings and buttons). If your custom widget has similar lists, you can extend the WPML_Elementor_Module_With_Items class to handle translation for each item individually.

Example of a custom class that features a list of other elements
/**
* Class WPML_Elementor_Slides
*/
class WPML_Elementor_Slides extends WPML_Elementor_Module_With_Items {

  /**
   * @return string
   */
  public function get_items_field() {
     return 'slides';
  }

  /**
   * @return array
   */
  public function get_fields() {
     return array( 'heading', 'description', 'button_text' );
  }

  /**
   * @param string $field
   *
   * @return string
   */
  protected function get_title( $field ) {
     switch( $field ) {
        case 'heading':
           return esc_html__( 'Slides: heading', 'wpml-string-translation' );

        case 'description':
           return esc_html__( 'Slides: description', 'wpml-string-translation' );

        case 'button_text':
           return esc_html__( 'Slides: button text', 'wpml-string-translation' );

        default:
           return '';
     }
  }

  /**
   * @param string $field
   *
   * @return string
   */
  protected function get_editor_type( $field ) {
     switch( $field ) {
        case 'heading':
        case 'button_text':
           return 'LINE';

        case 'description':
           return 'VISUAL';

        default:
           return '';
     }
  }

}

You can find more examples in the following folder of the WPML core plugin:

  • ..\wp-content\plugins\sitepress-multilingual-cms\addons\wpml-page-builders\classes\Integrations\Elementor\modules\

Make sure to load the classes on the init hook. As of WPML 4.5, loading the classes before the init action hook results in a fatal error.