Developer Notes

Description of our development environment for Wordpress customizations.

Skills needed: Wordpress, PHP, Javascript, Sass, Vue.js, jQuery, Bootstrap, Webpack, Makefile.

webpack

We use webpack to compile all our JS and CSS modules.

Webpack generates a manifest file in dist/manifest.json that contains the public path to all compiled modules.

The theme and plugins load all JS code through the function:

\cceh\capitularia\lib\enqueue_from_manifest ($key, $dependencies = array ())

where $key ist the key in the manifest file.

To build production files compile with:

webpack --config webpack.prod.js

To build development files and enable hot module reloading (HMR) compile with:

webpack serve --config webpack.dev.js

webpack serve adds some code to your JS to enable HMR. Once loaded into Wordpress this code opens a socket to the webpack-dev-server and awaits commands. When webpack-dev-server detects a source code change it compiles the changed modules into hot-update.js files and sends a reload command down the socket. The HMR code in your app then tries to reload the changed modules preserving application state. If it fails to do so it will fallback on reloading the whole page (and will lose application state).

Example webpack config:

const { merge } = require ('webpack-merge');
const chokidar = require ('chokidar');
const common = require ('./webpack.common.js');

const host    = 'capitularia.fritz.box';
const devHost = 'localhost';
const devPort = 8081;

module.exports = merge (common, {
    'mode'    : 'development',
    'devtool' : 'eval-source-map',
    'output'  : {
        'publicPath' : `http://${devHost}:${devPort}/`,
    },
    'module' : {
        'rules' : [
            {
                'test' : /\.s?css$/,
                'use'  : [
                    'style-loader',
                    {
                        'loader'  : 'css-loader',
                        'options' : {
                            'importLoaders' : 2,
                        },
                    },
                    'postcss-loader',
                    'sass-loader',
                ],
            },
        ],
    },
    'devServer' : {
        'host'       : devHost,
        'port'       : devPort,
        // Enable hot module reloading (HMR).
        'hot'        : true,
        'liveReload' : false,
        'static'     : {
            'directory' : './dist',
        },
        'devMiddleware' : {
            // write images because we still load them the traditional way
            'writeToDisk' : true,
        },

        // Needed because we access port devPort from port 80.
        'allowedHosts' : [host],
        'headers'      : { 'Access-Control-Allow-Origin' : `http://${host}` },

        // Watch for changes to PHP files and reload the page when one changes.
        // See: https://mikeselander.com/hot-reloading-using-webpack-with-php-file-changes/
        setupMiddlewares (middlewares, devServer) {
            chokidar
                .watch (['themes/**/*.php', 'plugins/*/*.php'], {
                    'alwaysStat'     : true,
                    'atomic'         : false,
                    'followSymlinks' : false,
                    'ignoreInitial'  : true,
                    'persistent'     : true,
                    'usePolling'     : true,
                })
                .on ('all', () => {
                    devServer.sockWrite (devServer.sockets, 'content-changed');
                });
            return middlewares;
        },
    },
});

i18n of Javascript

Internationalization consists of these steps:

  1. Extract the strings to translate from the source files.

  2. Translate the strings and compile them into a .json file (in jed format).

  3. Make the .json file available to your JS module.

  4. Use the Wordpress wp-i18n.js library to translate the strings at runtime.

Extract

There are 3 kinds of source files: PHP files, JS files and Vue single component files. PHP files can be extracted using the GNU xgettext utility. JS and Vue files can be extracted using the easygettext utility.

PHP Files

In PHP files use the __ (), _x (), and _n () functions.

JS Files

In JS files, add this boilerplate to the file:

/** The Wordpress Text Domain of the plugin. */
const LANG = 'cap-dynamic-menu';

function $gettext (msg) {
    return wp.i18n.__ (msg, LANG);
}
function $pgettext (context, msg) {
    return wp.i18n._x (msg, context, LANG);
}
function $ngettext (singular, plural, number) {
    return wp.i18n._n (singular, plural, number, LANG);
}

Then use like this:

const message  = $gettext  ('Message to translate');
const pmessage = $pgettext ('Hint to translators', 'Message to translate');
const nmessage = $ngettext ('Singular', 'Plural', number);

We must use the $gettext() names for our functions, or the stupid easygettext tool will not recognize the function. The functions used like this tag the message for the string extractor and also translate the string at runtime.

Vue Files

Vue single component files contain 3 sections: HTML, JS, and CSS. The CSS section does not need to be translated.

In the JS section of Vue files, use the $t () function:

const message = $t ('Message to translate');

This function tags the message for the string extractor and also translates the string at runtime.

In the HTML template section of Vue files, you may use two different methods to tag translatable strings:

<h2 v-translate>Header to translate</h2>
<span title="$t ('Tooltip to translate')"></span>

This is the Vue 3 boilerplate that enables translations in Vue files: Put this in main.js before initializing your Vue app.

Vue.js 3 boilerplate
const DOMAIN = 'my-text-domain';
const app    = createApp (App);

// wrapper to call the Wordpress translate function
function $t (text) {
    return wp.i18n.__ (text, DOMAIN);
}

// the vm.$t function
app.config.globalProperties.$t = $t;

// the v-translate directive
app.directive ('translate', function (el) {
    el.innerText = $t (el.innerText.trim ());
});

app.mount ('...');

Translate

Use poedit to translate the extracted strings.

To compile .po files into .json files in jed-format we use po2json.

Enqueue Translations

Wordpress boilerplate to make translations available in PHP and JS files:

Wordpress boilerplate
use cceh\capitularia\lib;

const DOMAIN = 'my-text-domain';

function enqueue_scripts ()
{
    $key = 'my-module'; // key from webpack.common.js

    // enqueue webpacked JS module
    lib\enqueue_from_manifest ("$key.js", [
        'another-module.js',
    ]);

    // enqueue extracted (minified) CSS
    lib\enqueue_from_manifest ("$key.css", [
        'another-module.css'
    ]);

    // translations in PHP files
    lib\load_textdomain (DOMAIN);

    // translations in JS files
    lib\wp_set_script_translations ("$key.js", DOMAIN);
}

See also: https://make.wordpress.org/core/2018/11/09/new-javascript-i18n-support-in-wordpress/