Skip to content

Instantly share code, notes, and snippets.

@jenkoian
Last active May 19, 2021 16:06
Show Gist options
  • Save jenkoian/1fe56335f18ad0f2b43b5f93e26660c1 to your computer and use it in GitHub Desktop.
Save jenkoian/1fe56335f18ad0f2b43b5f93e26660c1 to your computer and use it in GitHub Desktop.

Languages on a WordPress project

The setup

We have a WP multi-site with two distinct languages, it doesn't really matter what those languages are, but in this case it was English GB (:uk:) and Welsh (:wales:).

We tend to develop custom code via mu-plugins, this means we can often have a lot of mu-plugins but we would like to share a text domain. This is because we have a single client, all of the mu-plugins are for this client and thus it makes sense to have a single text domain for all of their custom code. This is analagous, from a text domain point of view, to if you just did everything within a single theme. Similarly, we'd use the same text domain for the custom theme we have built for the client.

So the setup I'm trying to achieve is as such:

mu-plugins
    project-plugin
    another-project-plugin
themes
    project-theme
languages
    project.pot
    project-cy.po
    project-cy.mo
    project-en_GB.po
    project-en_GB.mo

What I've ended up with

Some bash scripts

I created a few bash scripts to help achieve this:

makepot.sh - generate a project level .pot file, taking into account each mu-plugin and the theme.

#!/usr/bin/env bash
# Creates pot files for each of the mu-plugins and the theme.
# WARNING: It will overwrite the existing pot file so use with caution.

in_array() {
    local haystack=${1}[@]
    local needle=${2}
    for i in ${!haystack}; do
        if [[ ${i} == ${needle} ]]; then
            return 0
        fi
    done
    return 1
}

ignored_plugins=(
    "000-internal-stuff-you-may-want-to-ignore"
    "any-other-plugin-you-wish-to-ignore"
)

location=wp-content/languages/your-desired-domain-here.pot
iter=0
merge='';
for dir in ./wp-content/mu-plugins/* ; do

    # only for directories, and not symlinks
    if [[ -d "$dir" && ! -L "$dir" ]]; then
        # skip any plugins we are wanting to ignore
        if in_array ignored_plugins "$(basename $dir)"; then
            echo "skipping ${dir}"
            continue
        fi

        echo "Making POT file for ${dir}";

        if [[ $iter > 0 ]]; then
           merge='--merge';
        fi;

        bin/docker/wp i18n make-pot $dir $location --package-name="Your project name" --domain=your-desired-domain-here $merge

        ((iter++))
    fi;
done

echo "Making POT file for ./wp-content/themes/your-theme-name-here"

bin/docker/wp i18n make-pot ./wp-content/themes/your-theme-name-here $location --package-name=Your project name --domain=your-desired-domain-here --merge

pot2po.sh - copy across the .pot file to .po files (if they don't already exist)

#!/usr/bin/env bash
# Copies .pot files to the .po equivalents
# It won't copy over existing files, so if you need to re-generate, you need to delete the .po file first.

locales=(en_GB cy)

for i in "${locales[@]}"; do
    if [ ! -f "wp-content/languages/your-desired-domain-here-${i}.po" ]; then
        echo "languages/your-desired-domain-here-${i}.po does not exist, creating"
        cp -n wp-content/languages/your-desired-domain-here.pot "wp-content/languages/your-desired-domain-here-${i}.po"
    else
        echo "wp-content/languages/your-desired-domain-here-${i}.po already exists, skipping"
    fi
done

po2mo.sh - compile the .po files to .mo files.

#!/usr/bin/env bash
# Compiles .po to .mo files.

for file in wp-content/languages/your-desired-domain-*.po ; do
    #dest="${file::-2}mo" - does not work in old bash and macs
    #solution: https://superuser.com/a/1363835
    dest="${file::$((${#file} - 2))}mo"

    msgfmt -o "$dest" "$file"
done

echo -e "Done.\n"

They could be tidied up a bit, text domain in a .env or something, but for quick and dirty it's not bad.

Loading the translations

With that in place we then need to load the translations. We have a 000-init.php type thing within mu-plugins which loads with all our initialisation type stuff, in there we have anything that needs to run after plugins_loaded, so we load the text domain in there:

add_action(
	'plugins_loaded',
	static function() {
        // ...
        
        // Note: get_locale() here is filtered by multilingualpress and they use their own locales.
		// Most of the time this will match with WordPress's but for Welsh they differ, cy vs cy_GB.
		// For loading the translation file we want the WP one, so check and correct if that is the case.
		$locale = get_locale();
		if ( $locale === 'cy_GB' ) {
			$locale = 'cy';
		}
		load_textdomain( 'your-desired-domain-here', WP_LANG_DIR . '/your-desired-domain-here-' . $locale . '.mo' );
    }
);

Yeah so MLP use their own locales which for some locales (such as welsh) differs slightly to the WP ones. Not the end of the world.

So what have we done again?

So we can now use __( 'blah', 'your-desired-domain-here' ) throughout our mu-plugins and theme and, with the scripts above, have this all go to a single, project level translation file(s) (.pot|po|mo).

Other things

We ignore everything in wp-content/languages apart from these files mentioned above:

/wp-content/languages/*
!/wp-content/languages/your-desired-domain-here.pot
!/wp-content/languages/your-desired-domain-here*.po
!/wp-content/languages/your-desired-domain-here*.mo

And we use wp-translation-downloader to handle core and plugin translations. We included this in our deployment script so we don't have to store them in our repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment