How to Make Multilingual
Plugins and Themes
John P Bloch ● 2016-02-20 ● WordCamp Miami
About Me
John P Bloch
Lead Web Engineer at 10up
10up is hiring
Tell them I sent you
Seriously, we’re always hiring
@johnpbloch
john.bloch@10up.com
Assumptions
& Definitions
Assumptions
• You can write PHP
• You are writing plugins or themes for distribution on the
wordpress.org repositories
Definitions
Internationalization
The process of making your
code translatable at all.
Abbreviated as i18n
Localization
The process of using translated
text in internationalized code.
Abbreviated as l10n
Definitions
Internationalization
The process of making your
code translatable at all.
Abbreviated as i18n
Localization
The process of using translated
text in internationalized code.
Abbreviated as l10n
Why should you care?
Why should you care?
• Empathy
• More than half of all new WordPress installations are in languages
other than English
Why should you care?
• Empathy
• Grow your potential user-base
• Potentially make more money
• Make more of an impact in the world
How to Internationalize Code
Text Domains
A text domain is a namespace for your code’s translations
Text domains need to be unique within plugins or themes
Use your plugin or theme slug
Text Domains – Loading Themes
load_theme_textdomain(
$textdomain,
$path // where your translations are
)
Run on after_setup_theme action
$path needs to be an absolute path
load_plugin_textdomain(
$textdomain,
false, // deprecated
$path // where translation files are
)
Run on plugins_loaded action
$path is relative to wp-content/plugins
Text Domains – Loading Plugins
Plugin and Theme Headers
Plugins and themes have two
special headers:
Text Domain
Domain Path
Basic Translation Functions
__( $text, $textdomain )
_e( $text, $textdomain )
_n(
$singular,
$plural,
$count,
$textdomain
)
Basic Translation Functions
$draft_count = get_my_drafts_count();
printf( _n(
'I have one draft.',
'I have %s drafts.',
$draft_count,
'my-textdomain'
), $draft_count );
Basic Translation Functions
$draft_count = get_my_drafts_count();
if( 1 === $draft_count ) {
_e( 'I have one post.', 'my-textdomain' );
} else {
printf( __(
'I have %s drafts.',
'my-textdomain'
), $draft_count );
}
Basic Translation Functions
_x( $text, $context, $textdomain )
_ex( $text, $context, $textdomain )
_nx(
$singular,
$plural,
$count,
$context,
$textdomain
)
Basic Translation Functions
You can also leave longer comments for the translators
printf(
/* translators: The placeholder is the
current user’s first name */
__( 'Hello, %s!', 'my-textdomain' ),
$current_user->first_name
);
Advanced Translation Functions
number_format_i18n(
$number,
$decimals = 0
)
1234567.89
1,234,567.89
1.234.567,89
Advanced Translation Functions
date_i18n(
$format,
$timestamp = false,
$gmt = false
)
'F j Y'
'February 20 2016'
'Febbraio 20 2016'
Advanced Translation Functions
Homework
Look up these translation
functions:
wp_localize_script()
esc_html__()
esc_html_e()
esc_html_x()
esc_attr__()
esc_attr_e()
esc_attr_x()
Final Steps
Final Steps
• Generate .pot file
• Official repositories provide web tools
• Publish .pot file
• Usually packaged with plugin or theme in the languages directory
• Add translation files to your plugin or theme
• Or don’t. Plugins have language packs now.
Gotchas
Gotchas
• Line breaks
• Empty strings
• Formatted strings vs. interpolation
_e( "You live in $state", 'my-plugin' );
#: my-plugin.php:23
msgid "You live in $state"
msgstr ""
$state = 'Florida';
// Lookup is "You live in Florida"
printf(
__( 'You live in %s', 'my-plugin' ),
$state
);
Gotchas
• Line breaks
• Empty strings
• Formatted strings vs. interpolation
• Word order and position
printf(
__( 'Hi %s, you live in %s', 'my-plugin' ),
$user_name, $state
);
printf(
__(
'Hi %1$s, you live in %2$s',
'my-plugin‘
),
$user_name, $state
);
Best Practices
Best Practices
• Use decent English style
• Avoid slang and abbreviations
• Keep text together
• Break at paragraphs
• Use formatted strings instead of
concatenation
• Translate phrases not words
• No unnecessary HTML
• Make no assumptions about
the translated text
• Length
• Word order
• Direction
• Punctuation
• Avoid translating URLs that
don’t have alternate
languages available
Tools
• Pig Latin (http://w.org/plugins/piglatin/)
• WP i18n Tools (develop.svn.wordpress.org/trunk/tools/i18n/)
• PoEdit (https://poedit.net/)
• GlotPress (http://blog.glotpress.org/)
• Translate WordPress (https://translate.wordpress.org)
Questions?
Twitter: @johnpbloch
Website: https://johnpbloch.com
Email: john.bloch@10up.com

Writing Multilingual Plugins and Themes - WCMIA 2016

Editor's Notes

  • #6  I18n also works for institutionalization lol Internationalization is all about potential Localization is realization of that potential
  • #8 Less than half of new WordPress installations are in English Exercise in empathy coming up
  • #12 WP community is so inclusive. Alienating that many people is contrary to the community values
  • #14 All translations need to use your text domain so that WP knows where to look for translations
  • #15 Tells WP where to find translations for your text domain
  • #16 Tells WP where to find translations for your text domain
  • #17 Domain path is relative to your plugin or theme’s root directory
  • #23 Number format i18n has a second argument which is how many decimal points to keep Always returns a string, not an integer or float
  • #24 First argument is the date format, second is timestamp (defaults to now) third is whether to use GMT Date formats should always be run through translation! There is no standard date format across most languages.
  • #29 Only unix line endings Never try to translate an empty string
  • #31 Always use %s, never anything else. PHP type coercion will take care of it for you.
  • #32 Only unix line endings Never try to translate an empty string
  • #34 You can’t depend on word order staying the same. Use positional formatting
  • #36 Sometimes HTML is unavoidable. There’s no great way to solve this. Just keep in mind that translators aren’t necessarily going to know HTML