Developer Notes

Description of our development environment for Wordpress customizations.

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

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,
                        },
                    },
                    {
                        'loader'  : 'sass-loader',
                        'options' : {
                            'sassOptions' : {
                                'quietDeps' : true,
                            },
                        },
                    },
                ],
            },
        ],
    },
    '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.sendMessage (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 are extracted using the docs/cap-gettext-extractor.mjs node utility.

PHP Files

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

JS Files

In JS files, do this:

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

const message  = wp.i18n.__ ('Message to translate', DOMAIN);
const xmessage = wp.i18n._x ('Message to translate', 'Hint to translators', DOMAIN);
const nmessage = wp.i18n._n ('Singular', 'Plural', number, DOMAIN);

These functions translate the message at runtime and also tag the message for the string extractor at compile time.

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 = this.$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 that calls the Wordpress translate function
function $t (text) {
    return wp.i18n.__ (text, DOMAIN);
}

// make it available as this.$t on all instances
app.config.globalProperties.$t = $t;

// the v-translate directive
app.directive ('translate', (el) => {
    el.innerText = wp.i18n.__ (el.innerText.trim (), DOMAIN);
});

app.mount ('...');

Translate

Use poedit to translate the extracted strings.

To compile .po files into .json files we use a custom made python/po2json.py.

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);
}