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) sends the IP to your chosen provider 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

Name: perflocale_lang. Value: the active language slug (e.g. en, fr, fr). 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.

IP addresses - GeoIP feature (opt-in)

Disabled by default. When enabled (Settings → URL & Routing → GeoIP Redirect), the visitor IP from REMOTE_ADDR (or a trusted proxy header) is forwarded once per first-time visit to the configured GeoIP provider, which returns a country code.

  • 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 md5(IP) - the IP is not reversibly stored in the cache key.
  • Supported providers: IPinfo, IPinfo Lite, ipstack, IP-API, ipapi.co. Each provider has its own data-processing terms; review them before enabling.
  • 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, or an external agency). 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_mt capability.
  • 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.

Workflow assignments

If you use PerfLocale’s translator workflow, the plugin’s workflow table stores a WordPress user ID (the assigned translator) against each piece of content plus status, priority, deadline, and free-text notes fields. No email addresses, names, or other direct personal data. When a user is deleted, their assignments are automatically anonymised (the user ID is zeroed and status reset to unassigned); the surrounding row is retained so translation history isn’t lost. The notes field on every workflow row (not just rows assigned to the deleted user — other translators may have written their name or email into notes too) is scrubbed for the data subject’s email, login, display name, full name, and first/last name; name patterns are uniqueness-gated so a shared first/last name that belongs to another active user on the site isn’t redacted. The same scrub runs on both the admin-driven delete_user path and the GDPR Erase Personal Data path — admins don’t need to file a GDPR request to get a clean cleanup.

Translation memory

The translation_memory table caches every machine-translated segment so the same source text doesn’t have to be re-translated next time it appears. Each row stores the source text, the target text, the language pair, and a usage counter. No user IDs, no IP addresses, no metadata identifying the requesting admin.

Because TM stores raw text segments, sites that translate user-generated content (translated comments, contact-form submissions, custom UGC fields) can accidentally end up with a visitor’s name or email inside a TM row. To handle that, the Erase Personal Data flow scrubs both source_text and target_text of every TM entry that matches the data subject’s identifiers, using the same uniqueness-gated pattern set as the workflow-notes scrub. The same scrub also runs on the admin-driven delete_user path.

Background-job dispatch identity

Long-running operations (XLIFF imports, bulk machine translation, migration runs, full-site string scans, AI quality scoring) 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 active job the user dispatched; the worker’s next cap re-validation then fails the 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 seven small UI-state entries against the user profile (per-page list lengths on the Strings, Translations, Languages, Assignments, and Glossary admin pages, 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 workflow assignments (object, language, status, priority, deadline, notes, timestamps) plus their PerfLocale admin-UI preferences (per-page list lengths, hidden language columns). The exporter is paginated at 100 workflow rows per page so translators with thousands of assignments don’t time out the request handler; admin-UI preferences emit on page 1 only.
  • Tools → Erase Personal Data: on a single pass — (1) anonymises workflow rows (zeroes assigned_to, sets status to unassigned; the row + notes survive so other contributors’ history isn’t disturbed); (2) scrubs the data subject’s identifiers out of every workflow row’s notes; (3) scrubs the same patterns out of every matching translation_memory row’s source_text + target_text; (4) zeroes created_by on every active background-job row the user dispatched; (5) deletes every PerfLocale admin-UI preference outright. Returns integer counts in both items_removed (UI-meta deletions + job-row anonymisations) and items_retained (workflow-row anonymisations + workflow-note scrubs + TM scrubs) so the data subject sees exactly what survived in what form.
  • 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 - GeoIP wording only appears if GeoIP is on, MT wording only appears if MT is on (and when MT is on, the policy text also discloses the Translation-Memory cache and its erase-time scrub). Workflow + admin-preferences sections always appear.

The GeoIP redirect and browser-language redirect are both gated by a single filter. Any consent-management plugin can hook it to suppress auto-redirects 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, 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: your GeoIP provider, your MT provider, and any exchange-rate or analytics services you configure separately.
  • 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.