Changelog — JPKCom Simple Lang

Version history of the JPKCom Simple Lang WordPress plugin.

Version history of jpkcom-simple-lang. Entries are taken verbatim from the project's README. A hands-on guide with tips is available in the project guide.

1.2.9

  • Fixed: WordPress variant locales could not be saved. The check was a regex, /^[a-z]{2,3}(_[A-Z]{2})?$/, which silently rejected de_DE_formal, nl_NL_formal, de_CH_informal, pt_PT_ao90 and art_xemoji — the meta box offered them, save_post dropped them, and nothing said so. This plugin even ships its own de_DE_formal translation. Validation now runs against get_available_languages() plus en_US, which is both stricter (no invented locales) and complete
  • Fixed: hreflang lost the region. de_DE and de_AT both came out as hreflang="de", so a two-language set advertised the same value for two different URLs and the annotation became ambiguous — regional variants are exactly what hreflang is for. Tags are now proper BCP 47 (de-DE, de-AT, pt-BR), WordPress variant suffixes are dropped (de_DE_formalde-DE), and two versions resolving to the same tag contribute one entry instead of a contradictory pair
  • Added: an x-default link pointing at the version in the site's default language, matched on the BCP 47 tag so a de_DE_formal page still counts as the German version on a de_DE site. Omitted when no version carries the default language
  • Fixed: the lang attribute on <html> now carries the region too (lang="de-DE" instead of lang="de")
  • Fixed: a failed locale switch is no longer treated as a success. switch_to_locale() returns false when the language pack is missing; that return value was ignored, so a page set to an uninstalled language advertised lang="fr" and hreflang="fr" while serving the site language, and wp_footer restored a switch that had never happened
  • Added: the editor now warns when a post's language pack is not installed, and the stored language stays selectable in the meta box. Previously it vanished from the dropdown, so simply pressing Update silently cleared the post's language without anyone touching the field
  • Removed: the plugin's own locale filter. switch_to_locale() installs WordPress' own filter, so it was redundant when the switch succeeded — and when it failed it was the thing that made the request claim a language WordPress had not loaded. It also ran ahead of the core locale switcher, so a later switch_to_locale() by other code could be overridden
  • Fixed: the settings sanitiser raised a TypeError on a non-array value. It was typed ?array, which rejected the value before the is_array() guard inside could run, so a hand-edited options form produced a fatal instead of the intended fallback
  • Added: tests/test-language.php — 36 cases covering locale validation, BCP 47 conversion, the hreflang and x-default output and the settings sanitiser; 13 of them fail against 1.2.8. CI runs it on every pull request and push to main
  • i18n: new strings translated for de_DE and de_DE_formal, .pot, .mo and .l10n.php regenerated. A translators: comment that sat too far from its _n() call to reach the POT was moved next to it
  • i18n: the shipped translation files were carrying about 110 foreign strings — entries extracted from WordPress core's theme.json, WooCommerce's email editor and the Twenty Twenty-Five theme, from a POT that had once been generated in the wrong working directory. Regenerating from the plugin directory alone brings the catalogue down to the 47 strings this plugin actually has. The .po headers, including Plural-Forms, are unchanged

1.2.8

  • Changed: the update manifest generator now defaults a missing Network: header to false instead of true, matching WordPress' own default. No change for this plugin, which declares Network: true explicitly
  • CI: the lint and guard workflow now also runs on pushes to main. It only covered pull requests, so a direct push with bypass rights skipped every check
  • Changed: comments, workflow step names and CI output across the repository are now English throughout, and the developer notes in CLAUDE.md were translated and trimmed. No effect on the shipped plugin

1.2.7

  • Changed: Tested up to raised to WordPress 7.1
  • Changed: the bundled updater's runtime floor now matches the plugin's own minimum. It bailed out below WordPress 6.8 while the plugin header has required 6.9 for several releases, so the check could never fire on a supported installation
  • Docs: the remaining "WordPress 6.8" requirement statements now say 6.9, matching the plugin header
  • CI: the release manifest's fallback values for requires and tested now say 6.9 and 7.1. They only apply when the README metadata cannot be read, but a stale fallback would have published a minimum the plugin no longer supports

1.2.6

  • Changed: the plugin banners (assets/banner-1544x500.avif, assets/banner-772x250.avif) are now a plain #3c4955 surface with no lettering

1.2.5

  • CI: the release step no longer copies the staging directory into itself, so the ZIP has no empty jpkcom-simple-lang/jpkcom-simple-lang/ folder
  • CI: bumped the pinned GitHub Actions (checkout v7.0.1, setup-python v7.0.0, action-gh-release v3.0.2, fetch-metadata v3.1.0), still pinned to full commit SHAs
  • CI: the release ZIP now excludes the development-only tests/ and tools/ directories
  • CI: security and regression tests now run on every pull request, where a plugin has them

1.2.4

  • Security: update packages are now verified before installation — the verified file is handed to WordPress instead of being downloaded a second time, so the bytes that were checked are the bytes that get installed
  • Security: a missing or unfetchable SHA-256 checksum now aborts the update instead of installing unverified code (previously it silently skipped verification)
  • Security: pinned every GitHub Action to a full commit SHA and added Dependabot with a 7-day cooldown, so a moved tag can no longer change the release build
  • Security: tightened which download the updater claims, so sibling plugins cannot match each other's package
  • Fixed: sprintf() calls in the updater bound named arguments to a variadic parameter, which raises ArgumentCountError on PHP 8.3
  • Fixed: the "View Details" modal could fail with a TypeError when the manifest omitted requires_plugins
  • Performance: a failed manifest fetch is now cached for an hour instead of being retried on every admin request
  • Added: CI workflow on every pull request (PHP lint, named-argument check, YAML validation, action-pinning guard)
  • Housekeeping: removed stray editor backups from the release package

1.2.3 - 2026-06-16

  • Raised the minimum WordPress version to 6.9 and "Tested up to" to WordPress 7.0
  • Switched license metadata to the SPDX identifier GPL-2.0-or-later with the HTTPS license URI

1.2.2 - 2026-06-16

  • Security: updater prefers an exact match against the manifest download_url over the slug heuristic, so a tampered manifest can no longer bypass the checksum gate
  • Security: timing-safe checksum comparison (hash_equals()) with an is_string() guard against hash_file() failures
  • Security: manifest fetch via wp_safe_remote_get() (SSRF defense-in-depth)
  • Fixed PHP warning and missing contributor names in the plugin detail popup (display_name now provided)
  • Fixed PHP warning/deprecation on wp plugin list by completing the no_update transient entry (new_version, package, tested, requires_php)

1.2.1 - 2026-03-13

Bug Fixes

  • Fixed manual ZIP upload failing with "No valid URL" error caused by checksum verification running on local file paths instead of remote URLs
  • Fixed release ZIP missing top-level plugin directory, preventing WordPress from recognizing it as an update to the existing installation

1.2.0 - 2026-03-13

Bug Fixes

  • Fixed language selection not saving for locales without country code (e.g. ar for Arabic)
  • Updated locale validation regex to accept both short (xx) and full (xx_XX) locale formats

1.1.1 - 2025-12-17

Bug Fixes

  • Fixed duplicate "Settings saved" message on admin settings page
  • Fixed Oxygen Builder conditions not displaying dropdown options
  • Fixed Oxygen Builder condition callback signatures to match API requirements

Improvements

  • Updated Oxygen Builder conditions to use correct API structure with 'options' array
  • Added proper operator support (==, !=) for "Post Language Is" condition
  • Added Yes/No dropdown options for boolean Oxygen conditions
  • Added German translations for Oxygen condition options (Ja/Nein)
  • All Oxygen conditions now appear under "Simple Lang" category

Technical Changes

  • Renamed callback functions to match Oxygen API conventions:
    • jpkcom_simplelang_oxygen_post_language_is() with ($value, $operator) parameters
    • jpkcom_simplelang_oxygen_has_custom_language() with ($value, $operator) parameters
    • jpkcom_simplelang_oxygen_uses_default_language() with ($value, $operator) parameters
  • Removed redundant settings_errors() call in admin settings page

1.1.0 - 2025-12-17

New Features

  • Translation Links: New meta box for linking posts in different languages
  • Hreflang Tags: Automatic generation of SEO-friendly <link rel="alternate" hreflang=""> tags
  • Bidirectional Linking: Posts automatically link to each other when translation links are created
  • Complete Translation Sets: All posts in a translation group are automatically linked together
  • Smart Validation: Prevents duplicate languages in translation sets

Bug Fixes

  • Fixed locale detection for default site language in translation grouping
  • Fixed type casting issue in translation link display
  • Improved meta data consistency across all translation sync operations

Improvements

  • New helper function: jpkcom_simplelang_get_site_default_locale()
  • Enhanced hreflang output with deterministic sorting
  • Performance optimization: Single query for all translation posts (prevents N+1 queries)
  • Updated German translations (de_DE and de_DE_formal)

Developer Notes

  • New module: includes/hreflang-translations.php
  • New meta key: _jpkcom_simplelang_translations (multiple entries per post)
  • All translation sync operations use loop prevention for data integrity

1.0.0 - 2025-12-16

Initial Release

  • Language selection dropdown in post editor sidebar
  • Support for all WordPress core languages
  • Post type activation settings page
  • Frontend locale override (HTML lang attribute and SEO plugin compatibility)
  • Oxygen Builder conditional logic integration
  • Helper functions for developers
  • Template override system
  • Automatic GitHub-based updates
  • German translations included
  • Full documentation and API docs