{"id":45,"date":"2026-09-09T04:26:00","date_gmt":"2026-09-09T04:26:00","guid":{"rendered":"https:\/\/nirohelp.com\/docs\/45\/translating-nirohelp\/"},"modified":"2026-09-09T04:26:00","modified_gmt":"2026-09-09T04:26:00","slug":"translating-nirohelp","status":"publish","type":"nirohelp_doc","link":"https:\/\/nirohelp.com\/docs\/developers\/translating-nirohelp\/","title":{"rendered":"Translating NiroHelp"},"content":{"rendered":"<p>Every user-facing string passes through <code>__()<\/code> \/ <code>_e()<\/code> \/ <code>esc_html_e()<\/code> in PHP<br \/>\nand <code>__()<\/code> from <code>@wordpress\/i18n<\/code> in the React admin, all under the text domain<br \/>\n<code>nirohelp<\/code>.<\/p>\n<h2>Plugin header values<\/h2>\n<pre><code class=\"language-none\">Text Domain: nirohelp\nDomain Path: \/languages\n<\/code><\/pre>\n<h2>The .pot file<\/h2>\n<p><code>languages\/nirohelp.pot<\/code>. Hand it to a translator or open it in Poedit or Loco<br \/>\nTranslate; both produce the matching <code>.po<\/code> and <code>.mo<\/code>.<\/p>\n<p><code>languages\/easyhelp.pot<\/code> is a pre-rename leftover and is not used by anything.<br \/>\nIgnore it.<\/p>\n<h2>File naming<\/h2>\n<p>WordPress loads <code>.mo<\/code> files named with the locale:<\/p>\n<pre><code class=\"language-none\">languages\/nirohelp-fr_FR.po      # human-editable source\nlanguages\/nirohelp-fr_FR.mo      # compiled, this is what WordPress loads\nlanguages\/nirohelp-de_DE.mo\nlanguages\/nirohelp-bn_BD.mo\n<\/code><\/pre>\n<p>The admin SPA needs a third form. See <em>The SPA&#8217;s strings<\/em> below.<\/p>\n<h2>Regenerating the template<\/h2>\n<p><strong>Build first.<\/strong> The SPA&#8217;s strings are extracted from the compiled bundle, not<br \/>\nfrom <code>spa\/<\/code>:<\/p>\n<pre><code class=\"language-none\">yarn build\ncomposer run makepot\ncomposer run make-json\n<\/code><\/pre>\n<p><code>makepot<\/code> runs:<\/p>\n<pre><code class=\"language-none\">wp i18n make-pot . languages\/nirohelp.pot \\\n  --exclude=node_modules,vendor,docs,release,tests,website,spa\n<\/code><\/pre>\n<p>Note <code>spa<\/code> in the exclude list \u2014 that is deliberate, and it is the part most<br \/>\nworth understanding.<\/p>\n<h3>Why extraction runs over the bundle<\/h3>\n<p>WP-CLI&#8217;s string scanner reads <code>.js<\/code> and <code>.jsx<\/code>. It cannot parse <code>.tsx<\/code> at all,<br \/>\nso pointing it at <code>spa\/<\/code> yields <strong>zero<\/strong> strings, silently, while<br \/>\n<code>Controllers\\Admin\\App<\/code> goes on calling <code>wp_set_script_translations()<\/code> for<br \/>\ntranslations that could never exist. Extraction therefore runs over<br \/>\n<code>build\/admin-app.js<\/code>, where webpack has rewritten the calls into<br \/>\n<code>(0,i.__)(\"\u2026\",\"nirohelp\")<\/code> \u2014 a form the scanner does understand.<\/p>\n<p>Two consequences:<\/p>\n<ul>\n<li><strong><code>makepot<\/code> must run after <code>yarn build<\/code>.<\/strong> Without it, hundreds of strings<br \/>\n  vanish from the POT without a word. The script now refuses to run when<br \/>\n  <code>build\/admin-app.js<\/code> is missing, and <code>composer run release<\/code> builds first.<\/li>\n<li><strong>Do not rename or move the bundle.<\/strong> <code>make-json<\/code> keys each JSON file by<br \/>\n  <code>md5()<\/code> of the script&#8217;s <em>path<\/em>, so the PO references have to say<br \/>\n  <code>build\/admin-app.js<\/code> \u2014 exactly what <code>wp_enqueue_script()<\/code> registers. Rename<br \/>\n  it and every SPA translation silently stops loading. If you move it,<br \/>\n  regenerate both files.<\/li>\n<\/ul>\n<h3>The SPA&#8217;s strings<\/h3>\n<p><code>composer run make-json<\/code> converts each <code>.po<\/code> in <code>languages\/<\/code> into the JSON<br \/>\nfiles <code>wp_set_script_translations()<\/code> loads at runtime, one per script handle,<br \/>\nnamed <code>nirohelp-{locale}-{md5-of-path}.json<\/code> and registered against the <code>nirohelp-admin-app<\/code> script handle. PHP strings come from the <code>.mo<\/code>;<br \/>\nReact strings come from these. <strong>A locale needs both<\/strong>, so run <code>make-json<\/code><br \/>\nafter every <code>.po<\/code> update, not only after <code>makepot<\/code>.<\/p>\n<h2>Loading translations<\/h2>\n<p>Translations load automatically when a matching locale is active in <strong>Settings<br \/>\n-&gt; General -&gt; Site Language<\/strong>, provided the <code>.mo<\/code> (and, for the admin screens,<br \/>\nthe <code>.json<\/code>) exists.<\/p>\n<h2>Per-user language<\/h2>\n<p>Individual users can pick a language under <strong>Users -&gt; Profile -&gt; Language<\/strong>.<br \/>\nNiroHelp respects it: an admin reading WordPress in French sees the plugin in<br \/>\nFrench even when the site default is English.<\/p>\n<h2>What is not translatable<\/h2>\n<ul>\n<li><strong><code>cdn\/chat.min.js<\/code><\/strong>, the chatbot widget. It is a vendor bundle served from<br \/>\n  the CDN to sites that are not necessarily WordPress, and its own defaults<br \/>\n  (<code>Support<\/code>, <code>Type a message...<\/code>, the offline greeting) are English. Override<br \/>\n  them per site with the <code>data-agent-name<\/code>, <code>data-input-placeholder<\/code>,<br \/>\n  <code>data-welcome-message<\/code> and <code>data-offline-message<\/code> attributes.<\/li>\n<li><strong><code>assets\/tickets\/js\/embed.js<\/code><\/strong>, the embeddable ticket form. Its labels are<br \/>\n  hard-coded English. Copy the file and edit it if you need another language.<\/li>\n<li>A handful of button states in <code>assets\/common\/js\/init.js<\/code>.<\/li>\n<\/ul>\n<p>None of these load through <code>wp_set_script_translations()<\/code>, so a <code>.json<\/code> file<br \/>\ndoes not reach them.<\/p>\n<h2>Never run <code>yarn lint:js --fix<\/code><\/h2>\n<p>Unrelated to translation but relevant to touching these files: the lint script&#8217;s<br \/>\nown glob covers <code>cdn\/chat.min.js<\/code>, and <code>--fix<\/code> reformats that vendor bundle into<br \/>\na 500-line diff. It ignores a path argument, so narrowing the command does not<br \/>\nhelp.<\/p>\n","protected":false},"author":1,"featured_media":0,"template":"","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}}},"nirohelp_product":[],"nirohelp_topic":[12],"class_list":["post-45","nirohelp_doc","type-nirohelp_doc","status-publish","hentry","nirohelp_topic-developers"],"_links":{"self":[{"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/nirohelp_doc\/45","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/nirohelp_doc"}],"about":[{"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/types\/nirohelp_doc"}],"author":[{"embeddable":true,"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/users\/1"}],"wp:attachment":[{"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/media?parent=45"}],"wp:term":[{"taxonomy":"nirohelp_product","embeddable":true,"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/nirohelp_product?post=45"},{"taxonomy":"nirohelp_topic","embeddable":true,"href":"https:\/\/nirohelp.com\/docs\/wp-json\/wp\/v2\/nirohelp_topic?post=45"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}