Upgrade to Pro — share decks privately, control downloads, hide ads and more …

WordCamp Rhode Island 2015 : Introduction to i18n / l10n

Dave McHale
September 26, 2015

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

Dave McHale

September 26, 2015
Tweet

Other Decks in Technology

Transcript

  1. Introduction to i18n and l10n
    Dave McHale
    @thedavetheory
    @dmchale
    [email protected]
    [email protected]

    View Slide

  2. i n t e r n a t i o n a l i z a t i o n
    1 + 18 +1
    i18n
    l o c a l i z a t i o n
    1 + 10 + 1
    l10n

    View Slide

  3. What can be translated?

    View Slide

  4. Pulp Fiction

    View Slide

  5. 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

    View Slide

  6. 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

    View Slide

  7. 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

    View Slide

  8. 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

    View Slide

  9. Rush Hour

    View Slide

  10. 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

    View Slide

  11. Hackers

    View Slide

  12. 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
    */

    View Slide

  13. 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

    View Slide

  14. 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

    View Slide

  15. Creating the .pot file
    • When our code is prepared for
    translation, we will run it through a
    program that generates a .pot file
    • .pot = portable object template
    • File contains all translatable strings
    • Can be generated…
    – Through the WP repository
    – Using Loco Translate
    – Via command-line, using the WP i18n tools
    – Grunt
    – GlotPress
    https://codex.wordpress.org/I18n_for_WordPress_Developers
    https://wordpress.org/plugins/loco-translate/
    # Loco Gettext template
    #, fuzzy
    msgid ""
    msgstr ""
    "Project-Id-Version: i18n test\n"
    "Report-Msgid-Bugs-To: \n"
    "POT-Creation-Date: Thu Sep 24 2015 18:38:43 GMT-0400
    (Eastern Daylight Time)\n"
    "POT-Revision-Date: Thu Sep 24 2015 18:38:51 GMT-0400
    (Eastern Daylight Time)\n"
    "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    "Last-Translator: \n"
    "Language-Team: \n"
    "Language: \n"
    "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION\n"
    "MIME-Version: 1.0\n"
    "Content-Type: text/plain; charset=UTF-8\n"
    "Content-Transfer-Encoding: 8bit\n"
    "X-Poedit-SourceCharset: UTF-8\n"
    "X-Poedit-Basepath: .\n"
    "X-Poedit-SearchPath-0: .\n"
    "X-Poedit-KeywordsList:
    _:1;gettext:1;dgettext:2;ngettext:1,2;dngettext:2,3;"
    "__:1;_e:1;_c:1;_n:1,2;_n_noop:1,2;_nc:1,2;__ngettext:1,2;__ngette
    xt_noop:1,2;"
    "_x:1,2c;_ex:1,2c;_nx:1,2,4c;_nx_noop:1,2,3c;_n_js:1,2;_nx_js:1,2,3
    c;"
    "esc_attr__:1;esc_html__:1;esc_attr_e:1;esc_html_e:1;esc_attr_x:1,
    2c;"
    "esc_html_x:1,2c;comments_number_link:2,3;t:1;st:1;trans:1;transC
    hoice:1,2\n"
    "X-Generator: Loco - https://localise.biz/"

    View Slide

  16. 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

    View Slide

  17. .pot file results
    echo __('Hack the planet!', 'my-text-domain');
    #: /i18n-test.php:14 /i18n-test.php:17
    msgid "Hack the planet!"
    msgstr ""
    echo __("Hack the $noun!", 'my-text-domain');
    echo __($verb . ' the planet!', 'my-text-domain');
    • No results. The parser skips these entirely

    View Slide

  18. .pot file results
    echo __('Hack the', 'my-text-domain') . $noun . __('! Hack the
    planet!', 'my-text-domain');
    #: /i18n-test.php:18
    msgid "Hack the"
    msgstr ""
    #: /i18n-test.php:18
    msgid "! Hack the planet!"
    msgstr ""

    View Slide

  19. Napolean
    Dynamite

    View Slide

  20. 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

    View Slide

  21. 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()

    View Slide

  22. .pot file interpretations of sprint()
    echo sprintf( __('Your mom goes to
    %s', 'my-text-domain'), $noun );
    #: /i18n-test.php:14
    #, php-format
    msgid "Your mom goes to %s"
    msgstr "“
    echo sprintf( __('Your %s goes to
    %s', 'my-text-domain'),
    $relative_title, $noun );
    #: /i18n-test.php:15
    #, php-format
    msgid "Your %s goes to %s"
    msgstr ""

    View Slide

  23. 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

    View Slide

  24. 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 );

    View Slide

  25. .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 ""

    View Slide

  26. The Princess
    Bride

    View Slide

  27. 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 );

    View Slide

  28. 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 );

    View Slide

  29. Better Off Dead

    View Slide

  30. 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

    View Slide

  31. 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

    View Slide

  32. .pot interpretations of _n()
    echo sprintf( _n('I want my %d
    dollar!', 'I want my %d dollars!',
    $int_dollars_owed, 'my-text-
    domain'), $int_dollars_owed );
    #: /i18n-test.php:14 /i18n-test.php:20
    #, php-format
    msgid "I want my %d dollar!"
    msgid_plural "I want my %d dollars!"
    msgstr[0] ""
    msgstr[1] ""
    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 );
    }
    #: /i18n-test.php:18
    msgid "You don't owe me any money!"
    msgstr "“
    AND

    View Slide

  33. Austin Powers:
    International Man
    of Mystery

    View Slide

  34. 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?”

    View Slide

  35. 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

    View Slide

  36. .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 ""

    View Slide

  37. 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' );

    View Slide

  38. .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 ""

    View Slide

  39. 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');
    }

    View Slide

  40. 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!

    View Slide

  41. 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)

    View Slide

  42. 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

    View Slide