← All releases

PerfLocale 1.0.2

— Non-Latin slug corruption and resolution, missing-extension fatals

  • Machine Translation could not be switched on from the admin at all. Its settings tab was listed only once the feature was already enabled, and the checkbox that enables it lives inside that tab; the Addons card said "Enable here" but drew no button, because the button was reserved for registered addons and Machine Translation is built in. The tab is now always listed and the card has its button. Present since 1.0.0.
  • Three settings were silently cleared by unrelated saves. A tab only submits the controls it draws, but every key of a tab was read whether drawn or not, so a hidden control read as one the operator had unticked. Enabling Machine Translation set its auto-translate scope to "no languages"; saving URL & Routing cleared Edge Hint Redirect while the edge worker was off; saving Performance reset the background engine on sites without Action Scheduler. Each setting is now read only when its own control was drawn.
  • Fatal error on servers without the mbstring extension: the Strings admin page truncated long strings with a function WordPress does not polyfill, so the page died rather than degrading.
  • Translated content was served in the wrong language on requests that carry a non-Latin slug undecoded. WordPress stores such slugs percent-encoded and PerfLocale compared its lookups against that stored form, but two request shapes deliver the slug decoded and neither matched: a query-string request such as ?category_name=ニュース, which is what a plain-permalink site issues for every archive and page, and a URL carrying raw UTF-8 in the path. Japanese, Chinese, Korean, Arabic, Hebrew, Russian, Greek, Thai and Hindi were affected, as was any accented Latin slug such as the German "Über uns". Pretty-permalink sites were not affected on the normal path, since WordPress delivers those already encoded. Slugs are now normalised to the stored form before every comparison, using core's own routine; a pure-ASCII slug is returned unchanged.
  • Long non-Latin term names produced permanently corrupted slugs. Shortening cut by byte position and landed inside a percent-escape; the re-sanitising that followed dropped the stray percent sign but kept the hex digits, so the slug became different characters rather than fewer. Japanese, Chinese and Korean names from about 24 characters and Arabic, Hebrew and Russian from about 45 were affected every time, and the damaged slug is what hreflang, the sitemap and the switcher emitted.
  • A PO import could delete a language's translations and put nothing back. Gettext parsing drops entries it cannot decode without raising an error, so a file saved as ISO-8859-1 or Windows-1252 read as valid but empty; in Replace mode that deleted everything and imported nothing. The importer now refuses a non-UTF-8 file, and one whose entries all carry an empty translation, before anything is deleted. Exports were affected too: a carriage return anywhere made the whole file unparseable, and is now normalised on the way out.
  • Excluded paths never matched a non-Latin path, and the two places that checked them disagreed with each other. The setting stores what you type while the request arrives percent-encoded. There is now one matcher, it decodes both sides, and it requires a path boundary, so an excluded /api no longer also excludes /apifoo.
  • A refused settings save reported success. The database declines a value it cannot store rather than shortening it, so the write failed and the screen still said it saved. Re-saving a tab unchanged is still reported as success.
  • Language prefixes outside plain ASCII could not be read back. A locale such as sr_RS@latin, or one written in Cyrillic, was written into the URL correctly and then stripped by the reader, so every non-default page served the default language. Both query mode and subdirectory mode now accept the prefix the plugin itself writes.
  • Four smaller multibyte defects: machine-translation length caps counted bytes while calling them characters, job log lines were cut mid-character, adding a language with a non-Latin slug stored an unusable row and reported success, and the REST create route destroyed emoji flags.
  • A fatal error on every front-end page, on servers built without the ctype extension. Building the language tag for the Content-Language header and the hreflang tags called a function WordPress ships no replacement for, on every request, so such a server returned a fatal error instead of a page. That routine now needs no extension at all and produces identical output for every locale tested.
  • XLIFF export and import no longer die when the XML extensions are absent. Both reached for them without checking, and the export route had nothing to catch the failure. Each now stops with a message naming the package to install. The address checks guarding outbound machine translation and webhooks now fail closed rather than fatally, so those features stop working rather than the site.
  • Site Health was naming the wrong extensions. It raised a critical failure when mbstring was missing, though the only multibyte functions left are ones WordPress polyfills, and it listed json, which cannot be disabled on any supported PHP version. The check now lists only extensions that gate a real feature, names the feature beside each, and reports a recommendation rather than a critical failure.
  • WPForms confirmation messages are now translated. Field labels and the submit button were already translated at render; the confirmation is chosen at submit time from a fresh read of the form, so it needed handling there. Notifications, spam settings and the form ID are untouched, so submissions, emails and entries stay on one pipeline. A form embedded outside a single post or page still shows the source-language confirmation.
  • XLIFF imports write once per post instead of once per field. A post with a translated title, body and excerpt cost three full saves and now costs one; measured on real content, importing 40 posts went from 120 post writes to 40 with byte-for-byte identical stored output.
  • Behaviour change: an XLIFF import now creates one revision per imported post rather than one per translated field, and save_post, post_updated and the other save hooks fire once per post rather than once per field. If a plugin vetoes the write for a post, none of that post's fields are stored, where previously the fields processed before the veto had already been saved; nothing is rolled back, the write simply does not happen.
  • Behaviour change: libretranslate.com no longer skips the outbound-URL safety check, so a site configured to use that host pays one cached DNS lookup for it.
  • New setting: the Machine Translation tab gains a Custom Fields switch for translating ACF, Meta Box and Pods values, which previously had no control anywhere in the admin.
  • The Addons screen no longer labels a switched-off built-in feature "Not installed" in one place while calling it "Disabled" in another.
  • Two settings descriptions corrected: the API-key link no longer implies wp-config constants are the only way to store keys, and the edge-integration text now points at the redirect order you can actually edit rather than a list with no interface.