{"id":1871,"date":"2026-02-04T06:24:11","date_gmt":"2026-02-04T06:24:11","guid":{"rendered":"https:\/\/aki-hamano.blog\/?p=1871"},"modified":"2026-02-04T06:24:11","modified_gmt":"2026-02-04T06:24:11","slug":"wordpress-i18n","status":"publish","type":"post","link":"https:\/\/aki-hamano.blog\/en\/2026\/02\/04\/wordpress-i18n\/","title":{"rendered":"Tips for internationalization in WordPress"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">According to <a href=\"https:\/\/developer.wordpress.org\/themes\/classic-themes\/functionality\/internationalization\/\" target=\"_blank\" rel=\"noreferrer noopener\">the Theme Handbook<\/a>, internationalization is defined as &#8220;the process of developing your theme, so it can easily be translated into other languages&#8221;. Accordingly, if you want to list your product in the WordPress.org Theme Directory or Plugin Directory, all text must be translatable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When internationalizing in this sense, the key points are as follows:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Setting the text domain appropriately<\/li>\n\n\n\n<li>Applying translation functions (such as <code>__()<\/code>) to all text<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">However, this is internationalization in a narrow sense, and <a href=\"https:\/\/developer.wordpress.org\/block-editor\/how-to-guides\/internationalization\/\" target=\"_blank\" rel=\"noreferrer noopener\">the Block Editor Handbook<\/a> refers to internationalization as &#8220;the process to provide multiple language support to software, in this case WordPress&#8221;. In other words, simply setting the text domain and using translation functions may not be sufficient.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this article, based on my experience contributing to WordPress development, I would like to introduce some of the key aspects of internationalization in the broadest sense, as well as some aspects that are often overlooked.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Translation<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">String Concatenation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">While it is essential that all text is translatable, there may be cases where some text changes dynamically, for example.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-javascript\"><code>\/\/ \u274c Don't\nconst fieldName = getFieldName();\nconst errorMessage = __( 'There is invalid text in the ', 'my-plugin' ) + fieldName + __( 'field.', 'my-plugin' );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is a very bad example because, from a grammatical perspective, it fixes the order of the text. Because the order of subjects, verbs, and objects varies between languages, your implementation must accommodate this, even if some of the text is dynamic.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In WordPress, the recommended approach to address this issue is to use placeholders. You should also add Translator comments to provide translators with context about dynamic elements.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-javascript\"><code>\u2705 Do\nconst fieldName = getFieldName();\nconst errorMessage = sprintf(\n\t\/\/ translators: %s: field name.\n\t__( 'Invalid text in %s field.', 'my-plugin' ),\n\tfieldName\n);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Another unusual example of string concatenation causing problems is the &#8220;percentage&#8221; string in the Gutenberg project.<\/p>\n\n\n\n<pre class=\"wp-block-code language-jsx\"><code>\/\/ \u274c Don't\nfunction Test( percentage ) {\n\treturn &lt;p&gt;{ `${ percentage }%` }&lt;\/p&gt;;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Surprisingly, <a href=\"https:\/\/phrase.com\/blog\/posts\/number-localization\/#toc_5\" target=\"_blank\" rel=\"noreferrer noopener\">the percent signs can be swapped or even changed depending on the locale<\/a>, so string concatenation should be avoided here as well. There&#8217;s no meaningful text in the translation strings, so a translator&#8217;s comment would also be a good idea.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-jsx\"><code>\/\/ \u2705 Do\nimport { __, sprintf } from '@wordpress\/i18n';\n\nfunction Test( percentage ) {\n\treturn (\n\t\t&lt;p&gt;\n\t\t\t{ sprintf(\n\t\t\t\t\/* translators: %d: Percentage value. *\/\n\t\t\t\t__( '%d%%', 'my-plugin' ),\n\t\t\t\tpercentage\n\t\t\t) }\n\t\t&lt;\/p&gt;\n\t);\n}<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/pull\/66323\" target=\"_blank\" rel=\"noreferrer noopener\">Update percentage strings to be translatable by AhmarZaidi \u00b7 Pull Request #66323 \u00b7 WordPress\/gutenberg<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/pull\/68587\" target=\"_blank\" rel=\"noreferrer noopener\">Simplify `sprintf` translation for percentage widths by im3dabasia \u00b7 Pull Request #68587 \u00b7 WordPress\/gutenberg<\/a><\/li>\n<\/ul>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">Link Internationalization<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Links to external resources may be embedded in the text. Depending on the external resource, it may be translated into several locales, and the URL may be different. If there is such a possibility, make the link itself translatable.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-php\"><code>&lt;?php\n\/\/ \u274c Don't\n_e( 'Please refer to &lt;a href=\"https:\/\/example.com\/\"&gt;this handbook page&lt;\/a&gt; for more information.', 'my-plugin' );\n\n\/\/ \u2705 Do\nprintf(\n\t__( 'Please refer to &lt;a href=\"%s\"&gt;this handbook page&lt;\/a&gt; for more information.', 'my-plugin' ),\n\tesc_url( __( 'https:\/\/example.com\/', 'my-plugin' ) )\n);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Note that while this format is fine in PHP, React escapes the HTML, so you need to use <code>createInterpolateElement<\/code> to convert the tag names in the string into React elements.<\/p>\n\n\n\n<pre class=\"wp-block-code language-jsx\"><code>import { __ } from '@wordpress\/i18n';\nimport createInterpolateElement from '@wordpress\/element';\n\nfunction Test() {\n\treturn (\n\t\t&lt;p&gt;\n\t\t\t{ createInterpolateElement(\n\t\t\t\t__(\n\t\t\t\t\t'Please refer to &lt;a&gt;this handbook page&lt;\/a&gt; for more information.', 'my-plugin'\n\t\t\t\t),\n\t\t\t\t{\n\t\t\t\t\ta: &lt;a href={ __( 'https:\/\/example.com\/', 'my-plugin' ) } \/&gt;,\n\t\t\t\t}\n\t\t\t) }\n\t\t&lt;\/p&gt;\n\t);\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Sentence Concatenation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">To join two sentences together, a half-width space may be hard-coded between them.<\/p>\n\n\n\n<pre data-label=\"welcome-guide.js\" id=\"welcome-guide.js\" class=\"wp-block-code lang-jsx\"><code>\/\/ \u274c Don't\nimport { __ } from '@wordpress\/i18n';\n\nfunction Test() {\n\treturn (\n\t\t&lt;p&gt;\n\t\t\t{ __( 'It is sunny today.', 'my-plugin' ) }{ ' ' }\n\t\t\t&lt;strong&gt;{ __( 'Tomorrow will be rainy.', 'my-plugin' ) }&lt;\/strong&gt;\n\t\t&lt;\/p&gt;\n\t);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This renders in English as the following HTML (minor details omitted):<\/p>\n\n\n\n<pre class=\"wp-block-code lang-html\"><code>&lt;p&gt;It is sunny today. &lt;strong&gt;Tomorrow will be rainy.&lt;\/strong&gt;&lt;\/p&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In English, this is correct because we put spaces between sentences, but what about in Japanese?<\/p>\n\n\n\n<pre class=\"wp-block-code lang-html\"><code>&lt;p&gt;\u4eca\u65e5\u306f\u6674\u308c\u3067\u3059\u3002 &lt;strong&gt;\u660e\u65e5\u306f\u96e8\u3067\u3057\u3087\u3046\u3002&lt;\/strong&gt;&lt;\/p&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">There is a space after &#8220;<code>\u4eca\u65e5\u306f\u6674\u308c\u3067\u3059\u3002<\/code>&#8221; In Japanese, there are no spaces between sentences, so this feels a little strange.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In cases like this, as mentioned above, in PHP, you can include the HTML tag in a single translation string. In React, use <code>createInterpolateElement<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-jsx\"><code>\/\/ \u2705 Do\nimport { __ } from '@wordpress\/i18n';\nimport createInterpolateElement from '@wordpress\/element';\n\nfunction Test() {\n\treturn (\n\t\t&lt;p&gt;\n\t\t\t{ createInterpolateElement(\n\t\t\t\t__( 'It is sunny today. &lt;strong&gt;Tomorrow will be rainy.&lt;\/strong&gt;', 'my-plugin' ),\n\t\t\t\t{ strong: &lt;strong \/&gt; }\n\t\t\t) }\n\t\t&lt;\/p&gt;\n\t);\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Conversion (word formation)<br><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">&#8220;Conversion (word formation)&#8221; is a phenomenon in which the part of speech changes without changing the form of the word. For example, imagine the following translation:<\/p>\n\n\n\n<pre class=\"wp-block-code lang-php\"><code>\/\/ \u274c Don't\n&lt;h2&gt;&lt;?php _e( 'Post', 'my-plugin' ); ?&gt;&lt;\/h2&gt;\n&lt;button&gt;&lt;?php _e( 'Post', 'my-plugin' ); ?&gt;&lt;\/button &gt;\n\n&lt;h2&gt;&lt;?php _e( 'View', 'my-plugin' ); ?&gt;&lt;\/h2&gt;\n&lt;button&gt;&lt;?php _e( 'View', 'my-plugin' ); ?&gt;&lt;\/button &gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In English, you can determine whether a text is a noun or a verb depending on the context in which it is used. However, there are locales where different words are used for nouns and verbs, and a single word cannot cover both.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, in Japanese, you might want the following text:<\/p>\n\n\n\n<pre class=\"wp-block-code lang-php\"><code>&lt;h2&gt;\u6295\u7a3f&lt;\/h2&gt;\n&lt;button&gt;\u6295\u7a3f\u3059\u308b&lt;\/button &gt;\n\n&lt;h2&gt;\u30d3\u30e5\u30fc&lt;\/h2&gt;\n&lt;button&gt;\u898b\u308b&lt;\/button &gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The way to achieve this is to provide context for your translation strings.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-php\"><code>\/\/ \u2705 Do\n&lt;h2&gt;&lt;?php _ex( 'Post', 'noun', 'my-plugin' ); ?&gt;&lt;\/h2&gt;\n&lt;button&gt;&lt;?php _ex( 'Post', 'verb', 'my-plugin' ); ?&gt;&lt;\/button&gt;\n\n&lt;h2&gt;&lt;?php _ex( 'View', 'noun', 'my-plugin' ); ?&gt;&lt;\/h2&gt;\n&lt;button&gt;&lt;?php _ex( 'View', 'verb', 'my-plugin' ); ?&gt;&lt;\/button&gt;<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/pull\/65046\">Add context to `View` string in post actions by swissspidy \u00b7 Pull Request #65046 \u00b7 WordPress\/gutenberg<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/pull\/64249\" target=\"_blank\" rel=\"noreferrer noopener\">Data Views: Add context to trash string by kebbet \u00b7 Pull Request #64249 \u00b7 WordPress\/gutenberg<\/a><\/li>\n<\/ul>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Another interesting example that actually occurred in the Gutenberg project is the &#8220;conversion of proper nouns and adjectives.&#8221;<\/p>\n\n\n\n<pre class=\"wp-block-code lang-php\"><code>&lt;button type=\"button\"&gt;&lt;?php _e( 'Small', 'my-plugin' ); ?&gt;&lt;\/button &gt;\n&lt;button type=\"button\"&gt;&lt;?php _e( 'Medium', 'my-plugin' ); ?&gt;&lt;\/button &gt;\n&lt;button type=\"button\"&gt;&lt;?php _e( 'Large', 'my-plugin' ); ?&gt;&lt;\/button &gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This might seem fine at first glance, but there is a web service called &#8220;Medium,&#8221; which is a proper noun.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In Gutenberg, context has been added to the proper noun so that &#8220;Medium&#8221; can be translated separately from &#8220;Medium&#8221; as an adjective.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-php\"><code>\/\/ \u56fa\u6709\u540d\u8a5e\n&lt;a&gt;&lt;?php _ex( 'Medium', 'social link block variation name', 'my-plugin' ); ?&gt;&lt;\/a&gt;\n\n\/\/ \u5f62\u5bb9\u8a5e\n&lt;button type=\"button\"&gt;&lt;?php _e( 'Medium', 'my-plugin' ); ?&gt;&lt;\/button &gt;<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/issues\/70399\" target=\"_blank\" rel=\"noreferrer noopener\">The social platform name &#8220;Medium&#8221; in Social Icons block should be disambiguation by context \u00b7 Issue #70399 \u00b7 WordPress\/gutenberg<\/a><\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">Block Development<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Don&#8217;t Use Translation Functions in Save Function<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When developing a block, you may want to set default fallback text that can be changed and localized.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Also, if you want to save block content in the post conent, it&#8217;s common to define that information in the save function. For example, what would happen if you wrote the save function as follows:<\/p>\n\n\n\n<pre data-label=\"save.js\" id=\"save.js\" class=\"wp-block-code language-jsx\"><code>\/\/ \u274c Don't\nimport { RichText, useBlockProps } from '@wordpress\/block-editor';\n\nexport default function save( { attributes } ) {\n\tconst { content } = attributes;\n\treturn (\n\t\t&lt;div { ...useBlockProps.save() }>\n\t\t\t&lt;RichText.Content value={ content || __( 'Hello World', 'my-plugin' ) } \/>\n\t\t&lt;\/div>\n\t);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The intention of this code is to prioritize the text set by the user, while providing localized text as a fallback. While this code may seem fine at first glance, its implementation could cause the block to break. Imagine the following flow:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>User A has their WordPress locale set to English.<\/li>\n\n\n\n<li>User A inserts this block into a post and saves it.<\/li>\n\n\n\n<li>User B has their WordPress locale set to Japanese.<\/li>\n\n\n\n<li>User B opens the post posted by User A.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This is where the block breaks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The reason for this is block validation, which checks whether the actual HTML saved in the post content matches the HTML generated by the save function, and raises an error if they don&#8217;t match. The translation function dynamically changes the text based on the user&#8217;s locale, which means it might not match the text saved in the post content.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I haven&#8217;t found an ideal approach to this yet, but one approach is to make blocks dynamic and render fallback text server-side. For example, the &#8220;next page&#8221; block (<code>core\/query-pagination-next<\/code>) has translatable default text while respecting the block&#8217;s attributes.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/blob\/e6d4256b0c251e251ef4c45267a61c96e08b065f\/packages\/block-library\/src\/query-pagination-next\/index.php#L29-L30\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/WordPress\/gutenberg\/blob\/e6d4256b0c251e251ef4c45267a61c96e08b065f\/packages\/block-library\/src\/query-pagination-next\/index.php#L29-L30<\/a><\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">Don&#8217;t Define Default Text in block.json<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The only text in block.json that is automatically considered translatable is the fields defined in the <a href=\"https:\/\/github.com\/t-hamano\/wordpress-develop\/blob\/trunk\/src\/wp-includes\/block-i18n.json\" target=\"_blank\" rel=\"noreferrer noopener\">block-i18n.json<\/a> file, so defining a string as the default value for attributes like this will not make it translatable:<\/p>\n\n\n\n<pre data-label=\"block.json\" id=\"block.json\" class=\"wp-block-code lang-json\"><code>{\n\t\"apiVersion\": 3,\n\t\"name\": \"my-plugin\/my-block\",\n\t\"title\": \"My Block\",\n\t\"attributes\": {\n\t\t\"content\": {\n\t\t\t\"type\": \"string\",\n\t\t\t\"source\": \"html\",\n\t\t\t\"selector\": \"div\",\n\t\t\t\"default\": \"Hello World\"\n\t\t}\n\t}\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">One solution is to set the default value on the server side, as explained in the previous section.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Don&#8217;t Define Content in example Field of block.json<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>example<\/code> field is primarily used to display a preview of the block, but as mentioned above, the string defined in this field is not translated.<\/p>\n\n\n\n<pre data-label=\"block.json\" id=\"block.json\" class=\"wp-block-code lang-json\"><code>{\n\t\"apiVersion\": 3,\n\t\"name\": \"my-plugin\/my-block\",\n\t\"title\": \"My Block\",\n\t\"attributes\": {\n\t\t\"content\": {\n\t\t\t\"type\": \"string\",\n\t\t\t\"source\": \"html\",\n\t\t\t\"selector\": \"div\",\n\t\t\t\"default\": \"Hello World\"\n\t\t}\n\t},\n\t\"example\": {\n\t\t\"attributes\": {\n\t\t\t\"content\": \"Hello World\"\n\t\t}\n\t}\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The solution is to define <code>example<\/code> directly in the <code>regisiterBlockType<\/code> property, rather than in <code>block.json<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-javascript\"><code>\/\/ \u2705 Do\nimport { __ } from '@wordpress\/i18n';\nimport { registerBlockType } from '@wordpress\/blocks';\n\nregisterBlockType( 'my-plugin\/my-block', {\n\tapiVersion: 3,\n\ttitle: __( 'My Block', 'my-plugin' ),\n\t\/\/ ...\n\texample: {\n\t\tattributes: {\n\t\t\tcontent: __( 'Hello World', 'my-plugin' ),\n\t\t},\n\t},\n} );<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/pull\/68373\" target=\"_blank\" rel=\"noreferrer noopener\">i18n: Make example label for Comments Pagination Next block translatable by yogeshbhutkar \u00b7 Pull Request #68373 \u00b7 WordPress\/gutenberg<\/a><\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">RTL Languages<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">RTL (Right-to-Left) languages \u200b\u200bare languages \u200b\u200bin which text is written from right to left, and Arabic is a typical example. English and Japanese are LTR (Left-to-Right) languages. WordPress supports both LTR and RTL languages, so checking that your product works correctly in RTL languages \u200b\u200bis an important aspect of internationalization.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">StyleSheet<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">WordPress provides several convenient mechanisms for adding stylesheets for RTL languages.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One of these is <code>style.css<\/code>, the theme&#8217;s main stylesheet. If you want to load completely different styles for RTL languages, create <code>style-rtl.css<\/code> and write the styles for RTL languages \u200b\u200bthere.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Alternatively, you can use the <code>wp_style_add_data()<\/code> function to replace any stylesheet file.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-php\"><code>&lt;?php\nwp_enqueue_style( 'my-theme-style', get_template_directory_uri() . '\/content.css', array(), wp_get_theme()-&gt;get( 'Version' ) );\n\/\/ RTL styles.\nwp_style_add_data( 'my-theme-style', 'rtl', 'replace' );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In this example, place the <code>content-rtl.css<\/code> file at the same level as the <code>content.css<\/code> file.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Build Tools<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Creating your own stylesheets for RTL languages \u200b\u200bis difficult because you have to invert all the physical properties.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-css\"><code>\/* LTR *\/\nmargin-left: 16px;\npadding-right: 8px;\nleft: 0;\n\n\/* RTL *\/\nmargin-right: 16px;\npadding-left: 8px;\nright: 0;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This process wouldn&#8217;t be necessary if you standardized your code using <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/Guides\/Logical_properties_and_values\" target=\"_blank\" rel=\"noreferrer noopener\">logical properties<\/a> from the start, but WordPress core, Gutenberg, and the default theme use RTLCSS (or its wrapper library) to automate this transformation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Using it is very simple; just specify the &#8220;original CSS file&#8221; and the &#8220;CSS file for RTL languages&#8221; as shown below.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-bash\"><code>rtlcss style.css style-rtl.css<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Developing blocks is even easier. If you&#8217;re developing blocks according to a template, you&#8217;re probably building the source using <code>@wordpress\/scripts<\/code>, which automatically generates CSS files for RTL languages. These CSS files are also automatically loaded according to the site&#8217;s locale.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There are no special considerations when using RTLCSS, but you should be aware of Control Directives. The most commonly used directive is the <code>\/*rtl:ignore*\/<\/code> syntax, which prohibits automatic transformation of physical properties.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-css\"><code>.test {\n  \/* rtl:ignore *\/\n  left: 10px;\n}<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/Guides\/Logical_properties_and_values\" target=\"_blank\" rel=\"noreferrer noopener\">CSS logical properties and values &#8211; CSS | MDN<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/rtlcss.com\/index.html\" target=\"_blank\" rel=\"noreferrer noopener\">RTLCSS<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/pull\/61540\" target=\"_blank\" rel=\"noreferrer noopener\">Scripts: Add RTLCSS to wp-scripts. by ryelle \u00b7 Pull Request #61540 \u00b7 WordPress\/gutenberg<\/a><\/li>\n<\/ul>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">Icon Direction<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The most basic support for RTL languages \u200b\u200bis through CSS, and simply providing the appropriate styles using RTLCSS is sufficient, but one thing that is often overlooked is the &#8220;direction of images and icons.&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, let&#8217;s look at the links with chevron icons in the site editor.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"297\" height=\"163\" src=\"https:\/\/aki-hamano.blog\/wp-content\/uploads\/2026\/02\/i18n-ltr.png\" alt=\"\" class=\"wp-image-1793\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">If you invert the physics properties for an RTL language, the layout will look like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"297\" height=\"163\" src=\"https:\/\/aki-hamano.blog\/wp-content\/uploads\/2026\/02\/i18n-rtl-bad.png\" alt=\"\" class=\"wp-image-1794\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This icon&#8217;s direction is incorrect; it should semantically be facing right.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You could use CSS to rotate the icon 180 degrees only in RTL languages, but the approach commonly used in Gutenberg is to use the <code>isRTL()<\/code> function to detect the locale and load the opposite icon.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-jsx\"><code>import { __, isRTL } from '@wordpress\/i18n';\nimport { Button } from '@wordpress\/components';\nimport { chevronLeft, chevronRight } from '@wordpress\/icons';\n\nfunction BackButton() {\n\treturn (\n\t\t&lt;Button icon={ isRTL() ? chevronRight : chevronLeft }&gt;\n\t\t\t{ __( 'Back', 'my-plugin' ) }\n\t\t&lt;\/Button&gt;\n\t);\n}<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/pull\/64962\" target=\"_blank\" rel=\"noreferrer noopener\">Fix: Pagination arrows are pointing in the wrong direction in RTL languages by t-hamano \u00b7 Pull Request #64962 \u00b7 WordPress\/gutenberg<\/a><\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">Form Elements<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Despite being an RTL language, there are some contexts where you might want to force LTR. One example is <code>email<\/code> and <code>url<\/code> fields.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">These fields generally expect only Latin characters to be entered, so apply <code>direction: ltr<\/code>. If you&#8217;re using the RTLCSS mentioned above, use a directive to prevent automatic transformation of this property:<\/p>\n\n\n\n<pre class=\"wp-block-code lang-css\"><code>input&#91;type=\"email\"],\ninput&#91;type=\"url\"] {\n\t\/* rtl:ignore *\/\n\tdirection: ltr;\n}<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/medium.com\/bumble-tech\/interface-localisation-adapting-text-fields-for-rtl-languages-67a386006a17\" target=\"_blank\" rel=\"noreferrer noopener\">Interface localisation: adapting text fields for RTL languages | by Mitya Kuznetsov | Bumble Tech | Medium<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/issues\/65893\" target=\"_blank\" rel=\"noreferrer noopener\">InputControl: Fix text direction for URL and email fields in block editor for RTL languages by im3dabasia \u00b7 Pull Request #68188 \u00b7 WordPress\/gutenberg<\/a><\/li>\n<\/ul>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Additionally, for <code>textarea<\/code> elements, whether LTR should be enforced depends on the context: for example, in Gutenberg, LTR is enforced for <code>textarea<\/code> elements that expect code or HTML input.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/pull\/65891\" target=\"_blank\" rel=\"noreferrer noopener\">Code block: set LTR direction for RTL languages by sabernhardt \u00b7 Pull Request #65891 \u00b7 WordPress\/gutenberg<\/a><\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">Non-Latin characters<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Non-Latin characters are writing systems other than the alphabet (A-Z), and Japanese is also a non-Latin character. In particular, problems are likely to occur in logic that assumes it is a Latin character.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Character Decoding<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Here is an example of how improper character decoding can lead to unexpected results. For example, the following code implements logic to retrieve and display post slugs:<\/p>\n\n\n\n<pre class=\"wp-block-code language-jsx\"><code>\/\/ \u274c Don't\nimport { useSelect } from '@wordpress\/data';\n\nexport default function useSlugForDisplay() {\n\tconst slug = useSelect(\n\t\t( select ) =&gt; select( 'core\/editor' ).getEditedPostSlug(),\n\t\t&#91;]\n\t);\n\treturn slug;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This logic works fine if the post slug contains only Latin characters. But what if the slug contains a non-Latin character, such as &#8220;<code>\u6295\u7a3f<\/code>&#8220;?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In the above hook, you&#8217;ll get the encoded string <code>%e6%8a%95%e7%a8%bf<\/code>, which is incorrect for display.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To solve this, you can use <code>decodeURIComponent<\/code> or its wrapper function <code>safeDecodeURIComponent<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code language-jsx\"><code>\/\/ \u2705 Do\nimport { useSelect } from '@wordpress\/data';\nimport { safeDecodeURIComponent } from '@wordpress\/url';\n\nexport default function useSlugForDisplay() {\n\tconst slug = useSelect(\n\t\t( select ) =&gt; select( 'core\/editor' ).getEditedPostSlug(),\n\t\t&#91;]\n\t);\n\treturn safeDecodeURIComponent( slug );\n}<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/pull\/42930\" target=\"_blank\" rel=\"noreferrer noopener\">Document Settings: Decode the post URL for the button label by Mamaduka \u00b7 Pull Request #42930 \u00b7 WordPress\/gutenberg<\/a><\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">Generate Slug Based on User Input<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When generating a value internally based on user input, assuming only Latin characters, this may result in unintended behavior.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, in Gutenberg, <a href=\"https:\/\/github.com\/blakeembrey\/change-case\" target=\"_blank\" rel=\"noreferrer noopener\">the <code>change-case<\/code> library<\/a> is used to pass user input values \u200b\u200bthrough the <code>paramCase<\/code> function to generate strings for slugs and presets. However, because the <code>paramCase<\/code> function removes non-Latin characters, there is a risk that the value will become empty.<\/p>\n\n\n\n<pre class=\"wp-block-code lang-javascript\"><code>console.log( paramCase( 'Hello World' ) );\n\/\/ &gt; 'hello-world'\nconsole.log( paramCase( '\u3053\u3093\u306b\u3061\u306f\u4e16\u754c' ) );\n\/\/ &gt; ''<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">There are various approaches to solving this problem, but if we take a look at what has been done in Gutenberg in the past, we can consider the following approaches:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Reject input of non-Latin characters in the first place.<\/li>\n\n\n\n<li>Use some kind of index number or random key instead of relying on user input.<\/li>\n\n\n\n<li>Use a fallback value if the value generated from user input is empty.<\/li>\n<\/ul>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/issues\/39210\" target=\"_blank\" rel=\"noreferrer noopener\">Creating a new color in multibyte character, the style in editor will be broken. \u00b7 Issue #39210 \u00b7 WordPress\/gutenberg<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/pull\/38695\" target=\"_blank\" rel=\"noreferrer noopener\">Site Editor: Limit template part slugs to Latin chars by Mamaduka \u00b7 Pull Request #38695 \u00b7 WordPress\/gutenberg<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/pull\/69732\" target=\"_blank\" rel=\"noreferrer noopener\">Fix: save custom template with non-latin slug by t-hamano \u00b7 Pull Request #69732 \u00b7 WordPress\/gutenberg<\/a><\/li>\n<\/ul>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">Layout and Design<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Width of Element Changes<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When text is translated, the size of elements containing that text may change depending on the locale, and text may wrap.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">While it&#8217;s unrealistic to test all text in all locales, it&#8217;s important to try the following approaches in areas where problems are likely to occur, in order to prevent layout disruptions due to overflow or wrapping in advance.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Don&#8217;t cram elements whose width may vary into a narrow container.<\/li>\n\n\n\n<li>Apply <code>overflow-x: auto<\/code> to allow overflow.<\/li>\n\n\n\n<li>Use flex layout to wrap overflowing elements.<\/li>\n\n\n\n<li>Apply <code>word-break:{break-all|break-word|auto-phrase}<\/code> to prevent overflow.<\/li>\n\n\n\n<li>Apply <code>text-overflow: ellipsis<\/code> to hide the end of overflowing text with &#8220;\u2026&#8221;. However, since this visually truncates the text, it may be best not to use it too often from an accessibility perspective.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Just one real-world example in Gutenberg is the publish post panel: when you publish a post, a horizontal row of buttons appears in the sidebar. This layout works fine, at least in English and Japanese.<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-f56f613f wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<p class=\"wp-block-paragraph\">\u82f1\u8a9e<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"282\" height=\"231\" src=\"https:\/\/aki-hamano.blog\/wp-content\/uploads\/2026\/02\/post-publish-panel-en.png\" alt=\"\" class=\"wp-image-1831\"\/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<p class=\"wp-block-paragraph\">\u65e5\u672c\u8a9e<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"282\" height=\"231\" src=\"https:\/\/aki-hamano.blog\/wp-content\/uploads\/2026\/02\/post-publish-panel-ja.png\" alt=\"\" class=\"wp-image-1832\"\/><\/figure>\n<\/div>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">However, in German (<code>de_DE<\/code>) the text for these buttons is long, so the buttons wrap to prevent overflow.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"282\" height=\"287\" src=\"https:\/\/aki-hamano.blog\/wp-content\/uploads\/2026\/02\/post-publish-panel-de.png\" alt=\"\" class=\"wp-image-1830\"\/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Date Order<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The order of the year, month, and day varies by country.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>YMD: Japan, China, Korea, etc.<\/li>\n\n\n\n<li>MDY: Mainly the United States<\/li>\n\n\n\n<li>DMY: United Kingdom, France, Germany, etc.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">For example, if the input fields for year, month, and day are separate and the order is hard-coded as shown below, it may feel unnatural in certain locales. How can we change this order depending on the locale?<\/p>\n\n\n\n<pre class=\"wp-block-code lang-php\"><code>&lt;label for=\"year\"&gt;&lt;?php _e( 'Year', 'my-plugin' ); ?&gt;&lt;\/label&gt;\n&lt;input type=\"number\" name=\"year\" id=\"year\" \/&gt;\n\n&lt;label for=\"month\"&gt;&lt;?php _e( 'Month', 'my-plugin' ); ?&gt;&lt;\/label&gt;\n&lt;select name=\"month\" id=\"month\"&gt;&lt;\/select&gt;\n\n&lt;label for=\"day\"&gt;&lt;?php _e( 'Day', 'my-plugin' ); ?&gt;&lt;\/label&gt;\n&lt;select name=\"day\" id=\"day\"&gt;&lt;\/select&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">WordPress core treats these individual form elements as placeholders for translation strings, allowing them to be reordered based on locale.<\/p>\n\n\n\n<pre class=\"wp-block-code language-php\"><code>\/* translators: 1: Month, 2: Day, 3: Year, 4: Hour, 5: Minute. *\/\nprintf( __( '%1$s %2$s, %3$s at %4$s:%5$s', 'my-plugin' ), $month, $day, $year, $hour, $minute );<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/63573462543b78b00f32b853e887f5e63b0f21b7\/src\/wp-admin\/includes\/template.php#L865-L866\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/63573462543b78b00f32b853e887f5e63b0f21b7\/src\/wp-admin\/includes\/template.php#L865-L866<\/a><\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">On the other hand, Gutenberg has the <code>DateTimePicker<\/code> component that is useful for entering dates. This component has the <code>dateOrder<\/code> prop, and the order of the year, month, and day will automatically change depending on its value. Therefore, if you use this component, you can deal with this issue by making the argument itself translatable.<\/p>\n\n\n\n<pre class=\"wp-block-code language-jsx\"><code>import { DateTimePicker } from '@wordpress\/components';\n\nconst MyDateTimePicker = ( date, onChange ) =&gt; {\n\treturn (\n\t\t&lt;DateTimePicker\n\t\t\tcurrentDate={ date }\n\t\t\tonChange={ onChange }\n\t\t\tdateOrder={\n\t\t\t\t\/* translators: Order of day, month, and year. Available formats are 'dmy', 'mdy', and 'ymd'. *\/\n\t\t\t\t_x( 'dmy', 'date order', 'my-plugin' )\n\t\t\t}\n\t\t\/&gt;\n\t);\n};<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/pull\/62481\" target=\"_blank\" rel=\"noreferrer noopener\">TimePicker: Add `dateOrder` prop to sort day, month, and year by t-hamano \u00b7 Pull Request #62481 \u00b7 WordPress\/gutenberg<\/a><\/p>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>According to the Theme Handbook, internationalization is defined as &#8220;the process of developing your them [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_locale":"en_US","_original_post":"https:\/\/aki-hamano.blog\/?p=1696","footnotes":""},"categories":[10],"tags":[],"class_list":["post-1871","post","type-post","status-publish","format-standard","hentry","category-tips","en-US"],"_links":{"self":[{"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/posts\/1871","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/comments?post=1871"}],"version-history":[{"count":61,"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/posts\/1871\/revisions"}],"predecessor-version":[{"id":1936,"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/posts\/1871\/revisions\/1936"}],"wp:attachment":[{"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/media?parent=1871"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/categories?post=1871"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/aki-hamano.blog\/wp-json\/wp\/v2\/tags?post=1871"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}