JPKCom Simple Lang — Guide & Tips

Display individual WordPress pages in a different language on a per-post basis with JPKCom Simple Lang — installation, language selection, hreflang, Oxygen conditions and practical tips.

JPKCom Simple Lang is a lightweight solution for displaying individual pages or posts in a language other than your site's default. Rather than a full multilingual plugin, it does exactly one job: overriding the WordPress locale per post in the frontend — including the <html lang=""> attribute, hreflang tags and Oxygen Builder conditions.

Guide

Requirements

  • WordPress 7.0 or newer (tested up to WordPress 7.1)
  • PHP 8.3 or newer (with strict typing)
  • At least one additional language pack installed (optional but recommended)
  • Multisite is supported (the plugin is network-enabled)

Installation

  1. Download the latest release ZIP from the GitHub Releases page (opens in a new tab).
  2. In your admin panel: Plugins → Add New → Upload Plugin, choose the ZIP, Install Now, then Activate.

Alternatively upload the jpkcom-simple-lang folder via FTP to /wp-content/plugins/, or clone it for development:

cd /path/to/wordpress/wp-content/plugins/
git clone https://github.com/JPKCom/jpkcom-simple-lang.git

Install language packs

The selection dropdown only shows installed languages. Add more like this:

  1. Go to Settings → General and open the Site Language dropdown.
  2. Select the language you want (e.g. "Deutsch", "Français") and click Save Changes — WordPress downloads the language pack automatically (5–10 seconds).
  3. Optionally switch the default language back afterwards; the installed pack stays available.

An overview of all 200+ languages is available at translate.wordpress.org (opens in a new tab).

Usage

  1. Enable post types (optional): Under Settings → Simple Lang you decide which post types get a language selector. Posts and Pages are enabled by default.
  2. Select a language: When editing a post, a Frontend Language Select dropdown appears in the sidebar. Choose an installed language or keep the default.
  3. Link translations (optional): Use the Translation Links meta box to connect related posts in different languages. Links are bidirectional — link Post A to B and both link back; an entire translation group is wired together automatically.
  4. In the frontend: On view, WordPress switches to the selected language automatically. Interface strings (core, theme, plugins), the lang attribute and SEO meta tags (og:locale) are translated; linked posts emit <link rel="alternate" hreflang="XX"> tags in the <head>.

Missing language pack: if the locale switch fails because the pack is not installed, the plugin no longer treats that as a success since version 1.2.9 — previously the page advertised lang="fr" and a matching hreflang while serving the site language. The editor now warns when a post's language pack is missing, and the stored language stays selectable in the dropdown. Up to 1.2.8 it vanished from there, so simply pressing Update silently cleared the post's language without anyone touching the field.

Important: Simple Lang does not translate your content. Titles, post body, custom fields and media captions stay exactly as you write them — writing content in the target language is up to you.

Checking the language setup: the Abilities API

Since version 1.3.0 the plugin registers two read-only abilities. The Abilities API is a WordPress core registry of machine-readable capabilities; AI assistants, MCP clients and REST automation read this site's language setup as structured data through it:

Ability What it answers
jpkcom-simple-lang/list-languages which post types have language selection enabled, the site default, which language packs are installed and which locales published posts actually carry
jpkcom-simple-lang/check-translation-sets which translation sets end up with an incomplete hreflang annotation

The second ability is the real gain, because it makes visible something that used to happen silently: when two versions in one set resolve to the same hreflang value — two German pages, or German together with German (formal) — they produce one entry, and the other page is annotated nowhere. That is deliberate, since two contradictory entries for one language would be worse; it is just that nothing in the editor said a page had been left out. The check now names the set, the shared value and the pages sharing it.

Also reported are sets with no x-default (none of their versions is in the site language), links to pages that were deleted, links that only point one way, and pages whose language pack is no longer installed — where the front end deliberately stops switching while the page still claims that language.

Deliberately not reported is a set whose only peculiarity is an unpublished translation: a draft is ordinary work in progress, and a report that flags every one of them stops being read. If the set has something else wrong, the draft is still listed — it explains why fewer languages appear than the set contains.

Both abilities require the edit_posts capability — not read as with the content plugins, because this reports an editorial condition rather than published content. To switch it off entirely, in wp-config.php:

define( 'JPKCOM_SIMPLELANG_ABILITIES', false );

jpkcom_simplelang_ability_capability narrows it further; jpkcom_simplelang_ability_meta controls how the abilities are exposed (REST route, MCP visibility, annotations).

Tips & Tricks

  • Pick the right tool: Simple Lang targets occasional pages in a secondary language (landing pages, docs, legal pages, client presentations). If you need full site translations, language switchers or translation workflows, WPML or Polylang are the better fit.
  • Hreflang with no configuration: Linked, published posts get self-referencing hreflang tags, sorted deterministically and emitted early in the <head> (before most SEO plugins). Since version 1.2.9 an x-default is included too — it points at the version in the site's default language, and is omitted when none of the linked versions carries that language.
  • Hreflang now carries the region (1.2.9): The tags are proper BCP 47, i.e. de-DE, de-AT, pt-BR. Previously de_DE and de_AT both came out as hreflang="de" — two different URLs got the same value, and regional variants are exactly what hreflang is for. WordPress variant suffixes are dropped (de_DE_formalde-DE); two versions resolving to the same tag contribute one entry instead of a contradictory pair. The lang attribute on <html> carries the region as well now (lang="de-DE" instead of lang="de"). Re-check your hreflang markup after updating if monitoring or tests relied on the old two-letter values.
  • Locale format: Valid means whatever WordPress knows through get_available_languages(), plus en_US. Since 1.2.9 that includes variant locales such as de_DE_formal, de_CH_informal or pt_PT_ao90 — the meta box offered them before, but saving dropped them without a word.
  • Oxygen Builder conditions: With Oxygen installed, three conditions are available: "Post Language Is" (a specific language), "Post Has Custom Language" (any custom language set) and "Post Uses Default Language" (site default). Use them to show or hide elements per language.
  • Helper functions for developers: jpkcom_simplelang_get_post_language( $post_id ) returns the set locale (or null), jpkcom_simplelang_get_current_language() the active frontend language. For language markup use jpkcom_simplelang_get_bcp47( 'de_DE_formal' ) — it yields de-DE and is what the plugin itself uses for lang and hreflang since 1.2.9. jpkcom_simplelang_get_language_code( 'de_DE' ) still exists and still returns the bare language code (de); it is the poorer choice for markup, because a bare subtag cannot express the region.
  • Template overrides: Plugin files can be overridden in the child theme ({child-theme}/jpkcom-simple-lang/{file}), parent theme or in MU plugins ({WPMU_PLUGIN_DIR}/jpkcom-simple-lang-overrides/{file}). Extend the search paths via the jpkcom_simplelang_file_paths filter.
  • REST API & data: The language is stored as post meta under the key _jpkcom_simplelang_language and is reachable via the REST API when configured. There is no bulk assignment — use update_post_meta() with the same key for that.
  • Deactivation is safe: After disabling, posts simply display in the default language; the meta data remains and applies immediately if you reactivate.
  • Performance: One meta query per post, a single locale switch per request, no extra tables, no frontend JS/CSS.
  • Updates & security: The plugin updates securely via GitHub with SHA256 checksum verification; a preferred match against the manifest's download_url prevents a tampered manifest from bypassing the checksum gate.

Further reading