Auto translate WordPress theme and plugin strings with WP CLI

How to use?

  • Create fresh WordPress setup.
  • Clone the plugin wp-i18n into your /wp-content/plugins/ directory.
  • Import the database latest.sql with WP CLI commnd as below:
    wp db import latest.sql
  • Now use below commands to translate any plugin/theme from wp.org.

To translate the plugin use 2 WP CLI commands:

wp wpi18n download_plugin_po {slug} --lang={mr}
wp wpi18n generate_plugin_po {slug} --lang={mr}

Here,

E.g. To translate the Contact Form 7 plugin we can use below commands:

wp wpi18n download_plugin_po contact-form-7  --lang=mr
wp wpi18n generate_plugin_po contact-form-7  --lang=mr

First command download the .po file of plugin.
And second command generate the translated strings .po file.


And, To translate the theme use below WP CLI command.

wp wpi18n generate_theme_po {slug} --lang={mr}
wp wpi18n generate_theme_po  {slug} --lang={mr}

Example 2: In below example I’m translating ShortcodeHub plugin.

E.g. To download the plugin .po files Im using command wp wpi18n download_plugin_po shortcodehub --lang=mr

D:\xampp\htdocs\wpi18n\wp-content\plugins\wp-i18n (master)
λ wp wpi18n download_plugin_po shortcodehub --lang=mr
shortcodehub | CREATED - po-files/plugins/shortcodehub/mr/wp-plugins-shortcodehub-dev-mr.po | https://translate.wordpress.org/projects/wp-plugins/shortcodehub/dev/mr/default/export-translations
shortcodehub | CREATED - po-files/plugins/shortcodehub/mr/wp-plugins-shortcodehub-dev-readme-mr.po | https://translate.wordpress.org/projects/wp-plugins/shortcodehub/dev-readme/mr/default/export-translations
shortcodehub | CREATED - po-files/plugins/shortcodehub/mr/wp-plugins-shortcodehub-stable-mr.po | https://translate.wordpress.org/projects/wp-plugins/shortcodehub/stable/mr/default/export-translations
shortcodehub | CREATED - po-files/plugins/shortcodehub/mr/wp-plugins-shortcodehub-stable-readme-mr.po | https://translate.wordpress.org/projects/wp-plugins/shortcodehub/stable-readme/mr/default/export-translations

Now, Im using command wp wpi18n generate_plugin_po shortcodehub --lang=mr to auto generate the .po files.

E.g. It looks like below:

D:\xampp\htdocs\wpi18n\wp-content\plugins\wp-i18n (master)
λ wp wpi18n generate_plugin_po shortcodehub --lang=mr
shortcodehub | 4 | 178 | 4508 UPDATED Documentation | WITH डोक्युमेंटशन
shortcodehub | 4 | 177 | 0 NOT EXIST Support
...
...
shortcodehub | 1 | 2 | 0 NOT EXIST Fix: Updated wrong 5 star rating link from dashboard page
shortcodehub | 1 | 1 | 0 NOT EXIST Initial release
shortcodehub | 1 | COMPLETE - TRANSLATED 36 STRINGS
shortcodehub | 1 | po-files/plugins/shortcodehub/mr/wp-plugins-shortcodehub-stable-readme-mr-translated.po
shortcodehub | TOTAL TRANSLATED 36
ALL TRANSLATED STRINGS COUNT: 2901

Screenshot for reference:

After executing both commands we have 36 auto translated strings. Also we have 2901 available strings in Marathi language.

All translated strings are stored in directory:
/wp-i18n/po-files/plugins/shortcodehub/mr/

Now file wp-plugins-shortcodehub-dev-mr.po is original file and wp-plugins-shortcodehub-dev-mr-translated.po is translated file.

We can use this auto translated file and import into Glotpress.

To confirm the translated strings the log file is generated which show all the translated strings. E.g.
/wp-i18n/po-files/plugins/shortcodehub/mr/log.txt

This file show the content like below:

1 | Documentation | डोक्युमेंटशन
2 | Version %s | आवृत्ती %s
...
...
36 | Features | वैशिष्ट्ये

How it works?

In WordPress there are lot of strings are 100% translated. See https://translate.wordpress.org/stats/

I notice that there are lot of common strings which are already translated in WordPress core releases.

E.g. Strings Leave a comment, Comment Content, Comment Date, Completed are some of the common strings examples which already translated into WordPress core releases.

In this article I’m giving an example of Marathi (मराठी) (mr) which have now 160 contributors.

In Marathi language there are 9+ core release:

  • dev
  • 4.8.x
  • 4.7.x
  • 4.6.x
  • 4.5.x
  • 4.4.x
  • 4.3.x
  • 4.3.x
  • 4.1.x

Each release have 4 sub projects:

So, For dev project core release have 4 sub projects such as:

https://translate.wordpress.org/projects/wp/dev/mr/default/
https://translate.wordpress.org/projects/wp/dev/cc/mr/default
https://translate.wordpress.org/projects/wp/dev/admin/mr/default
https://translate.wordpress.org/projects/wp/dev/admin/network/mr/default

And, Core version 4.8.x have 4 sub projects such as:

https://translate.wordpress.org/projects/wp/4.8.x/ru/default
https://translate.wordpress.org/projects/wp/4.8.x/cc/ru/default
https://translate.wordpress.org/projects/wp/4.8.x/admin/ru/default
https://translate.wordpress.org/projects/wp/4.8.x/admin/network/ru/default

And so on.


For each sub project we have a Export option.

With this option we can export the translated project translated strings in below formats:

  • Android XML (.xml)
  • Portable Object Message Catalog (.po/.pot)
  • Machine Object Message Catalog (.mo)
  • .NET Resource (.resx)
  • Mac OS X / iOS Strings File (.strings)
  • Java Properties File (.properties)
  • JSON (.json)
  • Jed 1.x (.json)
  • NGX-Translate (.json)

Development Background

I have create a fresh WordPress setup and created a plugin wpi18n.

In this plugin I have register a post type which store the original string as a post title.

And its translated language version in post meta.

E.g.

Now,

  • I have downloaded the .po files of all the core projects from 4.1.x to dev with the help of WP CLI command wp wpi18n download_all_core_files

We have all the translated files of each core release of each language. To process all languages it takes too much time. So, I have added the individual language support for importing the translated strings.

The CLI command wp wpi18n import_translated_strings mr download the original & translated string in Marathi language.

After importing strings, I notice that there are lot of strings which are translated as different in different core projects.

E.g. The string Leave a comment is translated as below:

  • टिपणी करा (4-1-x)
  • प्रतिक्रिया लिहा (4-2-x)
  • टिपणी करा (4-3-x)
  • टिपणी करा (4-4-x)
  • टिपणी करा (4-5-x)
  • टिपणी करा (4-6-x)
  • टिपणी करा (4-7-x)
  • टिपणी करा (4-8-x)
  • टिपणी करा (4-9-x-development)

So, To keep the consistency I have used most repetitive strings as a translated strings. E.g.

  • (8 times) टिपणी करा
  • (1 times) प्रतिक्रिया लिहा

So, I have used string टिपणी करा to auto translate the string Leave a comment in theme and plugin.

Some other examples:

Example 2: String – Password: have 3 variations in Marathi (mr) language as below:

(4 times) कूटशब्द:
(2 times) पासवर्डः
(10 times) पासवर्ड:
--------- FROM PROJECTS ---------
(4-1-x) कूटशब्द:
(4-2-x) कूटशब्द:
(4-3-x) पासवर्डः
(4-5-x) पासवर्ड:
(4-6-x) पासवर्ड:
(4-7-x) पासवर्ड:
(4-8-x) पासवर्ड:
(4-9-x-development) पासवर्ड:

Example 3: String – Sites have 3 variations in Russian (ru) language as below:

(1 times) сайтов
(3 times) Сайты
--------- FROM PROJECTS ---------
(4-1-x-administration-network-admin) сайтов
(4-1-x) Сайты
(4-2-x) Сайты
(4-3-x) Сайты

To detect all the repeated strings I have created the command which generate the file of all repeated strings.


To discuss with other teammates and confirm it works for all languages I have created a log file which show the original string and its log of repetitive strings with most repetitive string.

Below are the some language files:

To translate the string into any language I have create a command which set the most repeated string into the meta box.

E.g. String Sites is most 3 time repeated with Сайты So, I have set this most repeated string and used it while translating the string with CLI.

I have created some commands to auto generate the .po file of any theme/plugin.

// Commands to download the .po files of theme/plugin.
wp wpi18n download_plugin_po {plugin-slug} --lang={language}
wp wpi18n download_theme_po {theme-slug} --lang={language}
// Commands to generate the .po files of theme/plugin.
wp wpi18n generate_theme_po {theme-slug} --lang={language}
wp wpi18n generate_plugin_po {plugin-slug} --lang={language}
// Other commands.
wp wpi18n translate_all_plugin_files --lang={language}
wp wpi18n generate_blank_lang_po {language}
wp wpi18n generate_filled_lang_po {language}
wp wpi18n import_translated_strings {language}
wp wpi18n set_most_repeated_string_as_top {language}
wp wpi18n detect_all_repeated_string {language}

To confirm strings, I have first used my own native language and I found that its 100% works. After that I have tried another language.

After translating plugins and themes I found that the Import Translation feature just import any .po file and detect the string into it.

Finally, I have create each language .po file which set the most repeated string as a translation string. And imported it into the theme/plugin. And it works as expected.


Known Issues

After discuss with yui, I found that the repeated strings are depends on plural/singular, male, female or neutral or sometimes its participle highly depends on context.

The string Sites is translated as in (4-1-x-administration-network-admin) as сайтов because it has the context.

And in (4-1-x) its Сайты because there is no context.

For reference see 4-1-x-administration.

While translating strings It does not check its context. I’ll discuss with community and maybe change this or maybe implement another way to do this same thing.


GlotPress Background

WordPress use the GlotPress to translate the strings from WordPress Core, Themes, Plugins etc.

In GlotPress Roadmap there is a target Translation Memory which is scheduled for version x+5. It is the almost same as reusing strings from existing approved strings.

I have read about this from below reference links:


Whats next?

I’ll discuss this with the Polyglot team on slack channel. You can also try this for your theme/plugin. Its super easy. Just you wan’t to setup the WP CLI on your local environment.

If you don’t have setup the WP CLI then read How to setup WP CLI (WordPress Command Line Interface).

How to add widget search into the WordPress Widgets page

Quick Preview

To add the search follow below steps:


Step 1: Add search input field in WordPress backend

  • Open file /wp-admin/widgets.php
  • Goto line 429
  • Press enter key and add below code:
<div class="widgets-search"&gt;
	<input type="search" id="widgets-search-input" class="regular-text" placeholder="<?php _e('Search widgets...'); ?&gt;"&gt;
</div&gt;

Step 2: Add CSS for the input field

  • Open file /wp-admin/css/widgets.css
  • Goto line 775
  • Press enter key and add below code:
/* =Widget Search
-------------------------------------------------------------- */
#widgets-search-input {
	margin: 0 0 .2em 0;
	width: 100%;
	font-size: 16px;
	font-weight: 300;
	line-height: 1.5;
	border-width: 1px;
	border-style: solid;
	border-color: #ddd;
}


.show-widget {
    display: block;
}

.hide-widget {
    display: none;
}

Step 3: Add JS for the search widgets

  • Open file /wp-admin/js/widgets.js
  • Goto line 504
  • Press enter key and add below code:
/*
 * Use feature detection to determine whether inputs should use
 * the `keyup` or `input` event. Input is preferred but lacks support
 * in legacy browsers. See changeset 34078, see also ticket #26600#comment:59
 */
if ( 'oninput' in document.createElement( 'input' ) ) {
	inputEvent = 'input';
} else {
	inputEvent = 'keyup';
}

$( '#widgets-search-input' ).on( inputEvent, function() {

	var search_term   = $('#widgets-search-input').val() || '',
		parent        = $('#widgets-left'),
		widgets       = parent.find('.widget'),
		widget_titles = parent.find('.widget-title');

	if( search_term.length ) {

		// Hide all widgets. Because, Below we show those widgets
		// which have search term in the widget title.
		widgets.addClass('hide-widget').removeClass('show-widget');

		// Search widget and ONLY show these widgets which "contain" the widget title.
		var rex = new RegExp( search_term, 'i');
        widget_titles.filter(function () {
			var widget_name = $.trim( $(this).text() ) || '';
        	return rex.test( widget_name );
        }).parents('.widget').removeClass('hide-widget').addClass('show-widget');

	} else {

		// Show all widgets.
		widgets.removeClass('hide-widget').addClass('show-widget');
	}
});

Great!

Or

Also, check below complete code snippet gist:

See how it widget search works in below video:


I have created a patch for the WordPress core. See https://core.trac.wordpress.org/ticket/47540