WordCamp Rhode Island 2015 : Introduction to i18n / l10n
Internationalization and Localization for WordPress developers. An introduction to why i18n is important, and how to get started making your plugin or theme translation-ready
Actually… no • In the 2014 State of the Word, Matt Mullenweg reported that WordPress had more non-English downloads than English downloads in that year - http://ma.tt/2014/10/sotw-2014/ • That is a BIG deal. Non-English growing quickly! • 7.2 Million non-English downloads versus 6.5 Million English
Why do we care • 71% of all current WordPress.com sites are English, 29% are non-English https://wordpress.com/activity/ • 74.4% of WordPress 4.3 downloads are US English, and 25.6% in non-English – Additional language packs are not counted in these numbers
Why do we care • WordPress is currently running over 24% of the internet • If we use the WordPress.com usage percentages, “only” 29% of that market share == 6.96% of the internet is running a non-English WordPress install
Why i18n matters for growth • Social Media sites like Facebook, Twitter and others experienced 30% growth in their first year after localizing • What's helped? Numerous factors, but rising global usage is an "easy" place to gain market share • Continued improvements to i18n process only continue to make the platform more accessible https://speakerdeck.com/petya/wordpress-is- growing-globally-are-you
Introduction to __() • The double-underscore function • Most common • Most basic, but fits most needs • Based on the _() (single underscore) gettext function, but specifically for WordPress in order to load translation files “In computing, gettext is an internationalization and localization (i18n) system commonly used for writing multilingual programs on Unix-like computer operating systems. The most commonly used implementation of gettext is GNU gettext, released by the GNU Project in 1995.” - https://en.wikipedia.org/wiki/Gettext
Using __() echo __('Hack the planet!', 'my-text-domain'); ‘my-text-domain’ is the text domain which you have defined for your plugin or theme /* Plugin Name: Hello Dolly Plugin URI: http://wordpress.org/plugins/hello-dolly/ Author: Matt Mullenweg … Text Domain: hello-dolly */
Never translate variables echo __("Hack the $noun!", 'my-text-domain'); echo __($verb . ' the planet!', 'my-text-domain'); echo __('Hack the planet!', $my_text_domain); • Variables will not get parsed properly inside double quotes • Using string concatenation will not help • Translation tools cannot interpret text domain dynamically
Translate full phrases, NOT this echo __('Hack the', 'my-text-domain') . $noun . __('! Hack the planet!', 'my-text-domain'); • Translators lose reference points when you break apart your strings in this way • We need to use a different method to use variables in an intelligent manner
Translating the .pot file • Someone (maybe even you!) will translate the strings in your code into another language • Once translation is complete, two files are created – .po and .mo files • These files are named for the language scheme they apply to – es_ES.po and es_ES.mo for Spanish – pl_PL.po and pl_PL.mo for Polish – etc • Those files are added to the /languages subfolder of your code
Enter sprintf() • While not technically a translation function, sprintf() gives us the ability to use variables in our translation strings echo sprintf( __(‘Your mom goes to %s', 'my-text-domain'), $noun ); • The inner part of the function leaves us only translating ‘Your mom goes to %s’ (or its foreign-language equivalent) • sprintf() will replace the %s with $noun • %s is the symbol for strings, but there are a number of type specifiers that can be used - http://php.net/manual/en/function.sprintf.php
sprintf() with multiple variables • What if we have more than one variable? echo sprintf( __('Your %s goes to %s', 'my-text-domain'), $relative_title, $noun ); • Percent variables are used as placeholders in the string we want to be translated • Their order in the original string will match the order of our variable parameters passed to sprintf()
sprintf() • What if the order of your variables needs to change? • In the translation, the noun may need to precede the verb echo sprintf( __('Your %1$s goes to %2$s', 'my-text-domain'), $relative_title, $noun ); • This is another really good reason not to break up your phrases into multiple gettext functions
sprintf() • By using the N$ notation within the placeholder variables, we can identify which variable goes with which placeholder. • %2$s will always match to $noun, no matter where %2$s goes in the translation string echo sprintf( __(‘Your %1$s goes to %2$s! Because %2$s is a bad thing!', 'my-text-domain'), $relative_title, $noun );
.pot file interpretations of sprintf() echo sprintf( __(‘Your %1$s goes to %2$s', 'my-text-domain'), $relative_title, $noun ); #: /i18n-test.php:16 #, php-format msgid "Your %1$s goes to %2$s" msgstr "" echo sprintf( __(‘Your %1$s goes to %2$s! Because %2$s is a bad thing!', 'my-text-domain'), $relative_title, $noun ); #: /i18n-test.php:17 #, php-format msgid "Your %1$s goes to %2$s! Because %2$s is a bad thing!" msgstr ""
One last look at __() and sprintf() echo __('Hello. My name is Inigo Montoya.', 'my-text-domain'); echo sprintf( __('Hello. My name is %1$s.', 'my-text-domain'), $my_name ); echo sprintf( __('%2$s. My name is %1$s.', 'my-text-domain'), $my_name, $greeting_text );
Using _e() and printf() to echo • As a shortcut, you can combine “echo” and “__()” into the _e() function _e(‘Hello. My name is Inigo Montoya.', 'my-text-domain'); • While sprintf() returns a string, which can be used however you like, you can alternatively call printf() to just echo the result of whatever is inside the function printf( __('You killed my %s. Prepare to die.', 'my-text-domain'), $relative );
Dealing with multiples for numeric values echo sprintf( _n('I want my %d dollar!', 'I want my %d dollars!', $int_dollars_owed, 'my-text-domain'), $int_dollars_owed ); • _n() takes 4 parameters – Singular string, Plural string, Numeric value, and Text Domain • In this example, $int_dollars_owed is first used inside of _n(), then used for replacement as part of sprintf() • %d is our placeholder in this example because we’re using digits
Custom conditions for numeric values • The codex recommends writing your own explicit conditionals for special cases if ($int_dollars_owed === 0) { echo __(‘You don't owe me any money!’); } else { echo sprintf( _n('I want my %d dollar!', 'I want my %d dollars!', $int_dollars_owed, 'my-text-domain'), $int_dollars_owed ); } https://codex.wordpress.org/I18n_for_WordPress_Developers
Dealing with homonyms and homographs • Homonym: same spelling, same pronunciation – “A bear can bear very cold temperatures” • Homograph: same spelling, different pronunciation – “The violin player held his bow while taking a bow” • In text, it can be difficult to convey our meaning with our words • #polyglots, “ […] in Jetpack: “Set the primary account holder”. Does anyone know if it is the holder of the primary account, or is it the primary holder of the account?”
Providing context with _x() echo _x( 'I like to see girls of that... caliber', 'by caliber I mean the high quality of their characters', 'my-text-domain' ); • _x() takes the string to be translated, the context for the person doing the translation, and the text domain Dr. Evil: I like to see girls of that... caliber. [pause] Dr. Evil: By "caliber," of course, I refer to both the size of their gun barrels and the high quality of their characters... Two meanings... caliber... it's a homonym... Forget it. ” – Doctor Evil http://www.imdb.com/character/ch0026630/quotes
.pot interpretation of _x() echo _x( 'I like to see girls of that... caliber', 'by caliber I mean the high quality of their characters', 'my-text-domain' ); #: /i18n-test.php:15 msgctxt "by caliber I mean the high quality of their characters" msgid "I like to see girls of that... caliber" msgstr ""
Leaving notes for translators • Sometimes you may have a need for adding a specific note for translators, which could be different than simply providing context of a single word or phrase using _n() • You can do this with the /* translators: comment notation • Your note will be added to the pot file for translators as long as it is the last comment before the gettext function call /* translators: draft saved date format, see http://php.net/date */ $draft_saved_date_format = __( 'g:i:s a' );
.pot interpretation of /* translators: /* translators: draft saved date format, see http://php.net/date */ $draft_saved_date_format = __( 'g:i:s a' ); #. translators: draft saved date format, see http://php.net/date #: /i18n-test.php:15 /i18n-test.php:72 msgid "g:i:s a" msgstr ""
Final notes, for your i18n to DO something • Text Domain MUST match your plugin/theme slug • Plugins add_action('init', 'my_plugin_init'); function my_plugin_init() { load_plugin_textdomain('my-text-domain', false, dirname(plugin_basename(__FILE__)) . '/languages'); } • Themes add_action('after_setup_theme', 'my_theme_setup'); function my_theme_setup() { load_theme_textdomain('my-text-domain', get_template_directory() . '/languages'); }
Summary • __(), _e() • sprintf(), printf() • _n() • _x() • /* translators: • What .pot, .po, and .mo files are and how they work together • Not covered: – Escaping content, more _n() love, dates, number formatting, and more!
Final Warnings, “Gotchas”, and Misc • Avoid – Newline characters – HTML in translations – URLs in translations • Unless it’s translatable – Empty strings • Notes – Many sentences at once is okay, but don’t translate War and Peace. Use paragraphs, at least. • More Good Stuff – Many exciting changes are coming to GlotPress soon – http://translate.wordpress.org – Localized versions of the plugin & theme repositories exist – Language Packs in the repo! – Streamlining the author/translator relationship right in the repository, similar to contributors (proposal)
Additional Resources • WP Links – https://codex.wordpress.org/I18n_for_WordPres s_Developers – https://developer.wordpress.org/plugins/internati onalization/ – https://developer.wordpress.org/themes/function ality/localization/ – https://make.wordpress.org/polyglots/handbook/ • Otto’s Blog - http://ottopress.com/tag/i18n/ • Slack – #meta-i18n and #polyglots • WP Repository – https://wordpress.org/plugins/ – https://wordpress.org/themes/ • Google! Contact • [email protected] [email protected] • @thedavetheory on Twitter • @dmchale on Slack • http://www.binarytemplar.com All images are the property and copyright of their original owners. No ownership is claimed by their use in this presentation