Privacy & GDPR
This page is a technical description of what data PerfLocale processes, where it’s stored, and which third parties receive it. It is not legal advice. Consult a privacy lawyer to finalise privacy-policy wording and data-processing agreements for your site.
At a glance
- No tracking. No analytics. No visitor fingerprinting.
- One cookie (
perflocale_lang) storing only the chosen language slug -HttpOnly,Secure,SameSite=Lax, 365-day default lifetime. - Visitor IP is never logged or stored. The GeoIP feature (disabled by default) ships with no lookup service of its own; if you wire one up, the IP is sent to it only to resolve a country code, which is then cached for 24 hours.
- Machine-translation features are admin-triggered. End-visitor content is not sent to MT providers automatically.
- Integrates with WordPress’s built-in privacy tools: Export, Erase, and Policy Guide.
What data is handled
Language-preference cookie
Name: perflocale_lang. Value: the active language slug (e.g. en, fr, de). Attributes: HttpOnly, Secure on HTTPS, SameSite=Lax. Lifetime: 365 days by default, configurable via the cookie_lifetime setting or the perflocale/cookie_lifetime filter.
This cookie contains no personal data - only the UI preference. Most privacy frameworks classify it as “strictly necessary” for a multilingual site (equivalent to a shopping-cart cookie on an e-commerce site) and it does not require consent on that basis. Your local counsel should confirm this for your jurisdiction.
If you prefer to set no cookie at all, switch on Cookieless mode (Settings → URL & Routing → Language Cookie) - the cookie is never written. Language routing is URL-based, so it keeps working; you only lose “remember my language” on non-prefixed URLs. The perflocale/privacy/consent_given filter (below) suppresses the cookie too, so a consent plugin can gate it automatically.
IP addresses - GeoIP feature (opt-in)
Disabled by default, and PerfLocale bundles no geolocation service — out of the box the feature contacts nothing at all. It only resolves a country when the site wires up a source of its own through the perflocale/geo/lookup_country or perflocale/geo/providers filters. When that is in place and the feature is enabled (Settings → URL & Routing → GeoIP Redirect), the visitor IP from REMOTE_ADDR (or a trusted proxy header) is passed to your lookup source once per first-time visit, and a country code comes back.
- The IP address itself is never persisted to the database or logged.
- The resolved country code is cached server-side for 24 hours (configurable) under a key derived from a site-salted HMAC of the anonymised address (host bits zeroed by
wp_privacy_anonymize_ip()) - the IP is not reversibly stored in the cache key, and one cache entry covers a whole subnet. - Because you choose the lookup source, its data-processing terms are yours to review. If it is a third-party API, you are the one sending visitor IPs to it.
- Disable in one filter:
add_filter('perflocale/privacy/consent_given', '__return_false');
Machine-translation content (admin-triggered)
Disabled by default. When enabled and used, the plugin sends post/page/string content - whatever the administrator or a translator initiates - to the configured MT provider (DeepL, Google, Microsoft, LibreTranslate, an external agency, or — on WordPress 7.0+ — the WordPress AI Client, which routes through whatever AI provider the site has configured). The response is stored as a draft translation for human review.
- End visitors never trigger MT - only admins, editors, or users with the
perflocale_use_mtcapability. - No visitor metadata is attached to MT requests. The payload is the source text plus language codes.
- If translating content that contains personal data, ensure you have a legal basis and a data-processing agreement with the chosen MT provider.
Accept-Language header (browser-language redirect feature)
When the browser-language redirect setting is on, the standard Accept-Language header is read once per first visit and matched against active site languages. The header is not stored, logged, or forwarded anywhere.
Background-job dispatch identity
Long-running operations (XLIFF imports, bulk machine translation, migration runs, full-site string scans, data exports) are queued as background jobs and persisted as rows in the dedicated {$wpdb->prefix}perflocale_jobs table. Each row records the user ID of the admin who dispatched it in the indexed created_by column so the worker can re-validate the capability when it eventually runs. On Erase Personal Data and on delete_user, created_by is zeroed on every job row that carried the user’s ID; the worker’s next cap re-validation then fails any still-pending job cleanly. Completed and failed jobs auto-purge after 24 hours via daily GC regardless.
Admin user preferences
For registered users with admin access, PerfLocale stores up to five small UI-state entries against the user profile: the per-page list length on the Strings, Translations, and Languages admin screens, plus which language columns are hidden on the Strings and Translations tables. These are pure UI preferences - no payment data, no behavioural tracking. They’re exported by Tools → Export Personal Data and deleted by Tools → Erase Personal Data or automatically when WordPress deletes the user.
WordPress Privacy tools integration
PerfLocale registers with all three WordPress privacy surfaces:
- Tools → Export Personal Data: exports the user’s PerfLocale admin-UI preferences (per-page list lengths, hidden language columns) plus a summary row for each background job they dispatched (job type, status, created-at). Both are small, fixed sets, so everything is emitted on page 1.
- Tools → Erase Personal Data: on a single pass — (1) zeroes
created_byon every background-job row that carried the user’s ID; (2) deletes every PerfLocale admin-UI preference outright. Returns integer counts:items_removedcovers the UI-meta deletions plus the job-row anonymisations, anditems_retainedis0— PerfLocale keeps nothing that identifies the data subject after the pass. - Settings → Privacy → Policy Guide: suggested privacy-policy text is registered via
wp_add_privacy_policy_content(). The sections shown adapt to which features are enabled - the browser-language, GeoIP, edge-redirect, and machine-translation paragraphs each appear only when that feature is switched on. The language-cookie and admin-preferences sections always appear.
Consent-management integration
The perflocale_lang cookie, the GeoIP redirect, and the browser-language redirect are all gated by a single filter. Any consent-management plugin can hook it to hold them back until the visitor has consented:
add_filter( 'perflocale/privacy/consent_given', function (): bool {
// Example: Complianz
if ( function_exists( 'complianz_has_consent' ) ) {
return (bool) complianz_has_consent( 'functional' );
}
// Example: Cookiebot
if ( class_exists( '\Cybot\Consent' ) ) {
return \Cybot\Consent::has_consent( 'preferences' );
}
return true;
} );When the filter returns false, the perflocale_lang cookie is not set, no outbound request is made (including to the GeoIP provider), and no redirect is issued. On the next page load, once the visitor grants consent, the filter returns true and normal behaviour resumes.
What you (as site owner) still need to do
- Publish a privacy policy describing these processes in your own words. The Policy Guide page gives you suggested text you can adapt.
- Sign Data Processing Agreements with any third parties you enable: the geolocation source you wire up, your MT provider, and any exchange-rate or analytics services you configure separately. PerfLocale bundles no geolocation or exchange-rate service, so each of those is a choice you make and a contract you own.
- Install a consent-management plugin if you need pre-consent gating. PerfLocale itself doesn’t show a banner - that’s better handled by a dedicated plugin (Cookiebot, Complianz, Iubenda, OneTrust, etc.).
- For EU deployments of DeepL, configure the EU endpoint / key so content stays in-region.