Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update internationalization process with complete updated example. #13909

Merged
merged 9 commits into from
Mar 11, 2019
245 changes: 215 additions & 30 deletions docs/designers-developers/developers/internationalization.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,232 @@
# Internationalization

This document aims to give an overview of the possibilities for both internationalization and localization when developing with WordPress.
## What is Internationalization?

## PHP
Internationalization is the process to provide multiple language support to software, in this case WordPress. Internationalization is often abbreviated as **i18n**, where 18 stands for the number of letters between the first _i_ and the last _n_.

For years, WordPress has been providing the necessary tools and functions to internationalize plugins and themes. This includes helper functions like `__()` and similar.
Providing i18n support to your plugin and theme allows it to reach the largest possible audience, even without requiring you to provide the additional language translations. When you upload your software to WordPress.org, all JS and PHP files will automatically be parsed. Any detected translation strings are added to [translate.wordpress.org](https://translate.wordpress.org/) to allow the community to translate, ensuring WordPress plugins and themes are available in as many languages as possible.

### Common Methods
For PHP, WordPress has a long established process, see [How to Internationalize Your Plugin](https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/). The release of WordPress 5.0 brings a similar process for translation to JavaScript code.

- `__( 'Hello World', 'my-text-domain' )`: Translate a certain string.
- `_x( 'Block', 'noun', 'my-text-domain' )`: Translate a certain string with some additional context.
- `_e( 'Hello World', 'my-text-domain' )`: Translate and print a certain string.
- `esc_html__( 'Hello World', 'my-text-domain' )`: Translate a certain string and escape it for safe use in HTML output.
- `esc_html_e( 'Hello World', 'my-text-domain' )`: Translate a certain string, escape it for safe use in HTML output, and print it.
- `_n( '%s Comment', '%s Comments', $number, 'my-text-domain' )`: Translate and retrieve the singular or plural form based on the supplied number.
Usually used in combination with `sprintf()` and `number_format_i18n()`.
## How to use i18n in JavaScript

## JavaScript
WordPress 5.0 introduced the wp-i18n JavaScript package that provides the functions needed to add translatable strings as you would in PHP.

Historically, `wp_localize_script()` has been used to put server-side PHP data into a properly-escaped native JavaScript object.
First, add **wp-i18n** as a dependency when registering your script:

The new editor introduces a new approach to translating strings for the editor through a new package called `@wordpress/i18n`.
```php
<?php
/**
* Plugin Name: Myguten Plugin
* Text Domain: myguten
*/
function myguten_block_init() {
wp_register_script(
'myguten-script',
plugins_url( 'block.js', __FILE__ ),
array( 'wp-blocks', 'wp-element', 'wp-i18n' )
);

The new script package is registered with WordPress as `wp-i18n` and should be declared as a dependency during `wp_register_script()` and imported as a global off the Window object as `wp.i18n`.
register_block_type( 'myguten/simple', array(
'editor_script' => 'myguten-script',
) );
}
add_action( 'init', 'myguten_block_init' );
```

Depending on your developer workflow, you might want to use WP-CLI's `wp i18n make-pot` command or a build tool for Babel called `@wordpress/babel-plugin-makepot` to create the necessary translation file. The latter approach integrates with Babel to extract the I18N methods.
In your code, you can include the i18n functions. The most common function is **__** (a double underscore) which provides translation of a simple string. Here is a basic static block example, this is in a file called `block.js`:

### Common Methods in wp.i18n (May Look Similar)
```js
const { __ } = wp.i18n;
const el = wp.element.createElement;
const { registerBlockType } = wp.blocks;

- `setLocaleData( data: Object, domain: string )`: Creates a new I18N instance providing translation data for a domain.
- `__( 'Hello World', 'my-text-domain' )`: Translate a certain string.
- `_n( '%s Comment', '%s Comments', numberOfComments, 'my-text-domain' )`: Translate and retrieve the singular or plural form based on the supplied number.
- `_x( 'Default', 'block style', 'my-text-domain' )`: Translate a certain string with some additional context.
- `sprintf()`: JavaScript port of the PHP function with the same name.
registerBlockType( 'myguten/simple', {
title: __('Simple Block', 'myguten'),
category: 'widgets',

### Loading Translations
edit: () => {
return el(
'p',
{ style: { color:'red'}, },
__('Hello World', 'myguten')
);
},

WordPress 5.0 introduces a new function called `wp_set_script_translations( 'my-script-handle', 'my-text-domain' )` to load translation files for a given script handle.
save: () => {
return el(
'p',
{ style: { color:'red'}, },
__('Hello World', 'myguten')
);
}
});
```

In the above example, the function will use the first argument for the string to be translated. The second argument is the text domain which must match the text domain slug specified by your plugin.

Common functions available, these mirror their PHP counterparts are:

- `__( 'Hello World', 'my-text-domain' )` - Translate a certain string.
- `_n( '%s Comment', '%s Comments', numberOfComments, 'my-text-domain' )` - Translate and retrieve the singular or plural form based on the supplied number.
- `_x( 'Default', 'block style', 'my-text-domain' )` - Translate a certain string with some additional context.

**Note:** Every string displayed to the user should be wrapped in an i18n function.

After all strings in your code is wrapped, the final step is to tell WordPress your JavaScript contains translations, using the [wp_set_script_translations()](https://developer.wordpress.org/reference/functions/wp_set_script_translations/) function.

```php
<?php
function myguten_set_script_translations() {
wp_set_script_translations( 'myguten-script', 'myguten' );
}
add_action( 'init', 'myguten_set_script_translations' );
```

This is all you need to make your plugin JavaScript code translatable.

When you set script translations for a handle WordPress will automatically figure out if a translations file exists on translate.wordpress.org, and if so ensure that it's loaded into `wp.i18n` before your script runs. With translate.wordpress.org, plugin authors also do not need to worry about setting up their own infrastructure for translations and can rely on a global community with dozens of active locales. Read more about [WordPress Translations](https://make.wordpress.org/meta/handbook/documentation/translations/).

## Provide Your Own Translations

You can create and ship your own translations with your plugin, if you have sufficient knowledge of the language(s) you can ensure the translations are available.

### Create Translation File

The translation files must be in the JED 1.x JSON format.

To create a JED translation file, first you need to extract the strings from the text. Typically, the language files all live in a directory called `languages` in your plugin. Using [WP-CLI](https://wp-cli.org/), you create a `.pot` file using the following command from within your plugin directory:

```
mkdir languages
wp i18n make-pot ./ languages/myguten.pot
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm running into wp-cli/i18n-command#138 when executing this command. To continue testing, what I did was to copy the below myguten.pot as if the command succeeded.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have php_mbstring installed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was it! After installing the PHP mbstring module, this worked as expected.

Although this should be noted somewhere in i18n as a dependency, would you think it'd merit a note here as well?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted on that issue you referenced, we might remove the mbstring dependency in the i18n command anyway.

```

This will create the file `myguten.pot` which contains all the translatable strings from your project.

```
msgid ""
msgstr ""
"Project-Id-Version: Scratch Plugin\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/scratch\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2019-03-08T11:26:56-08:00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"X-Generator: WP-CLI 2.1.0\n"
"X-Domain: myguten\n"

#. Plugin Name of the plugin
msgid "Scratch Plugin"
msgstr ""

#: block.js:6
mkaz marked this conversation as resolved.
Show resolved Hide resolved
msgid "Simple Block"
msgstr ""

#: block.js:13
#: block.js:21
msgid "Hello World"
msgstr ""
```

Here, `msgid` is the string to be translated, and `msgstr` is the actual translation. In the POT file, `msgstr` will always be empty.

This POT file can then be used as the template for new translations. You should **copy the file** using the language code you are going to translate, this example will use the Esperanto (eo) language:

```
cp myguten.pot myguten-eo.po
```

For this simple example, you can simply edit the `.po` file in your editor and add the translation to all the `msgstr` sets. For a larger, more complex set of translation, the [Glotpress](https://glotpress.blog/) and [poedit](https://poedit.net/) tools exist to help.

You need also to add the `Language: eo` parameter. Here is full `myguten-eo.po` translated file

```
# Copyright (C) 2019
# This file is distributed under the same license as the Scratch Plugin plugin.
msgid ""
msgstr ""
"Project-Id-Version: Scratch Plugin\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/scratch\n"
"Last-Translator: Marcus Kazmierczak <marcus@mkaz.com>\n"
"Language-Team: Esperanto <marcus@mkaz.com>\n"
"Language: eo\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2019-02-18T07:20:46-08:00\n"
"PO-Revision-Date: 2019-02-18 08:16-0800\n"
"X-Generator: Poedit 2.2.1\n"
"X-Domain: myguten\n"

You can learn more about it in [the JavaScript I18N dev note](https://make.wordpress.org/core/2018/11/09/new-javascript-i18n-support-in-wordpress/).
#. Plugin Name of the plugin
msgid "Scratch Plugin"
msgstr "Scratch kromprogrameto"

#: block.js:6
mkaz marked this conversation as resolved.
Show resolved Hide resolved
msgid "Simple Block"
msgstr "Simpla bloko"

#: block.js:13 block.js:21
msgid "Hello World"
msgstr "Saltuon mundo"
```

The last step to create the translation file is to convert the `myguten-eo.po` to the JSON format needed. For this, you can use the [po2json utility](https://github.com/mikeedwards/po2json) which you install using npm. It might be easiest to install globally using: `npm install -g po2json`. Once installed, use the following command to convert to JED format:

```
po2json myguten-eo.po myguten-eo.json -f jed
mkaz marked this conversation as resolved.
Show resolved Hide resolved
```

This will generate the JSON file `myguten-eo.json` which looks like:

```json
{
"domain": "messages",
"locale_data": {
"messages": {
"": {
"domain": "messages",
"lang": "eo"
},
"Scratch Plugin": [
"Scratch kromprogrameto"
],
"Simple Block": [
"Simpla bloko"
],
"Hello World": [
"Saltuon mundo"
]
}
}
}
```


### Load Translation File

The final part is to tell WordPress where it can look to find the translation file. The `wp_set_script_translations` function accepts an optional third argument that is the path it will first check for translations. For example:

```php
<?php
function myguten_set_script_translations() {
wp_set_script_translations( 'myguten-script', 'myguten', plugin_dir_path( __FILE__ ) . 'languages' );
}
add_action( 'init', 'myguten_set_script_translations' );
```

WordPress will check for a file in that path with the format `${domain}-${locale}-${handle}.json` as the source of translations. Alternatively, instead of the registered handle you can use the md5 hash of the relative path of the file, `${domain}-${locale} in the form of ${domain}-${locale}-${md5}.json.`

This example uses the handle, rename the `myguten-eo.json` file to `myguten-eo-myguten-script.json`.

### Test Translations

You will need to set your WordPress installation to Esperanto language. Go to Settings > General and change your site language to Esperanto.

With the language set, create a new post, add the block, and you will see the translations used.

## More Resources

- [WP-CLI I18N command to generate translation catalogues](https://github.com/wp-cli/i18n-command)
- [Plugin Developer Handbook](https://developer.wordpress.org/plugins/internationalization/)
- [Theme Developer Handbook](https://developer.wordpress.org/themes/internationalization/)
16 changes: 2 additions & 14 deletions packages/i18n/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# Internationalization (i18n)

Internationalization utilities for client-side localization.

<https://codex.wordpress.org/I18n_for_WordPress_Developers>
Internationalization utilities for client-side localization. See [How to Internationalize Your Plugin](https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/) for server-side documentation.

## Installation

Expand All @@ -23,17 +21,7 @@ sprintf( _n( '%d hat', '%d hats', 4, 'text-domain' ), 4 );
// 4 hats
```

Note that you will not need to specify [domain](https://codex.wordpress.org/I18n_for_WordPress_Developers#Text_Domains) for the strings.

## Build

You can use the [WordPress i18n babel plugin](/packages/babel-plugin-makepot/README.md) to generate a `.pot` file containing all your localized strings.

The package also includes a `pot-to-php` script used to generate a php files containing the messages listed in a `.pot` file. This is useful to trick WordPress.org translation strings discovery since at the moment, WordPress.org is not capable of parsing strings directly from JavaScript files.

```sh
npx pot-to-php languages/myplugin.pot languages/myplugin-translations.php text-domain
```
For a complete example, see the [Internationalization section of the Gutenberg Handbook](https://wordpress.org/gutenberg/handbook/designers-developers/developers/internationalization/).

## API

Expand Down