This article documents how the string translation subsystem of the wpresidence-translate plugin (text domain wpr-translate) is wired internally: the admin screen, the storage table, how rows land there, and how the admin runtime provides translations back to gettext when editing. Read the user-facing version for the day-to-day workflow and the broader multi-language real estate website guide for product context.
Files Involved
| File | Responsibility |
|---|---|
includes/admin/views/string-translation.php |
The admin grid view. Renders the filter bar, scan/generate buttons, and per-language textarea grid. |
includes/admin/views/string-helpers.php |
View-side helpers (e.g. option-value lookup by dotted path for theme admin strings). |
includes/admin/string-actions.php |
Handles the Delete selected translations bulk form submit. |
includes/admin/string-database.php |
Table existence check and the processed column upgrader. |
includes/admin/string-query.php |
Request parsing (filters, pagination) and query assembly for the grid. |
includes/admin/string-storage.php |
Locale/code maps, row merge/insert/update batching. |
includes/admin/string-export.php |
MO file generation from stored rows. |
includes/admin/string-targets.php |
Builds the list of scan targets (theme, parent theme, active plugins). |
includes/admin/theme-strings.php |
Imports theme admin strings declared in wpr/theme_admin_strings.json. |
includes/admin/theme-strings-helpers.php |
Flatten/expand wildcards for the JSON theme-admin string map. |
includes/theme-widget-strings.php / includes/admin/theme-widget-strings.php |
Widget title and text collectors. |
includes/admin/widget-instance-strings.php |
Widget-instance specific string emitters (sidebar label metadata). |
Storage Table
All strings live in {$wpdb->prefix}wpestate_translation_strings. Relevant columns:
string_id BIGINT primary key
context e.g. "theme:wpresidence", "plugin:woocommerce"
name str_<md5(value)> or a dotted theme-option key
value the source (English) string
language_code target language code, one row per language per string
md5 md5 of the source value, used to detect rewording
translation translated string for this language_code
status 0 = untranslated, 1 = translated / default
processed 0 = needs export to MO, 1 = already exported
updated_at DATETIME
A string therefore maps to N rows, one per configured language. The default language row always has status = 1 and stores the source value verbatim. The processed column is added lazily by wpr_translate_admin_ensure_processed_column() when the export path runs.
Admin Menu Page
Registered in includes/admin/menu.php at slug wpr-translate-strings with the render callback wpr_translate_admin_render_strings_page. The render callback delegates to includes/admin/views/string-translation.php with a $data payload: strings, languages, filters, domains, pagination, default_language_code, range_start, range_end, displayed.
Per-request data is built by wpr_translate_admin_get_strings_for_view( $filters, $pagination ) in string-query.php. Pagination defaults to 20 rows and can be filtered with wpr_translate_strings_per_page.
Form Actions on the Page
Three POST forms live inside string-translation.php. Each posts back to the same admin page with its own nonce:
| Nonce field | Action | Handler |
|---|---|---|
wpr_translate_scan_nonce |
Scan theme + plugins | wpr_translate_admin_scan_strings() |
wpr_translate_generate_nonce |
Build MO files | wpr_translate_admin_generate_translation_files() |
wpr_translate_reset_nonce |
Clear all stored strings | wpr_translate_admin_reset_strings() |
wpr_translate_delete_nonce |
Delete selected rows | wpr_translate_admin_handle_delete_strings() |
All handlers are wired through wpr_translate_admin_handle_strings_actions() which in turn is attached inside the admin bootstrap for the strings page.
AJAX Endpoints
wp_ajax_wpr_translate_save_translation→wpr_translate_admin_ajax_save_translation— inline save from textarea blur.wp_ajax_wpr_translate_auto_translate_strings→wpr_translate_admin_ajax_auto_translate_strings— auto-translate button on the strings page.
Scan Pipeline
wpr_translate_admin_scan_strings()loadswpr_translate_languagesand the scan targets.wpr_translate_admin_get_scan_targets()builds one target per source: active theme (theme:<slug>), parent theme when present, and every active plugin that has alanguagessubdirectory. Multisite mergesactive_sitewide_plugins. Single-file plugins are skipped.- For each target the scanner computes the latest mtime inside the
languages/directory and short-circuits viawpr_translate_scan_statewhen nothing has changed and the language signature (md5(wp_json_encode($languages))) matches. wpr_translate_admin_collect_strings_from_path()locates.mo/.pofiles, extracts entries, and hashes them bymd5($context . '|' . $value). Thenamecolumn is populated asstr_<md5(value)>.wpr_translate_admin_merge_detected_strings()folds overlapping results.wpr_translate_admin_persist_detected_strings()looks up existing rows via a chunkedSELECT IN()query, updates rows in place, and batches inserts 50 rows at a time inside a transaction.
Theme Admin Strings (JSON-Declared)
A theme can declare admin-option strings to be translated via wp-content/themes/<theme>/wpr/theme_admin_strings.json. wpr_translate_admin_import_theme_admin_strings() reads the file, flattens nested paths, expands wildcards against live option values, and stores the source domain in wpr_translate_theme_admin_strings_domain (with wpr_translate_theme_admin_strings_hash for change detection). The view then uses wpr_translate_admin_get_option_value_by_path() to resolve dotted names (e.g. wpestate_labels.search_button) against the current option value, so the grid always shows the live admin string.
Runtime Lookup
During normal frontend requests, WordPress serves translated strings from the compiled MO files (see the Gettext Pipeline & MO Files article). There is one exception: while you are editing on the wpr-translate-strings admin page, the gettext/gettext_with_context filters in includes/translation-hooks.php call wpr_translate_maybe_provide_translation(), which delegates to wpr_translate_lookup_runtime_translation(). That gate lives in wpr_translate_runtime_should_use_database_translations() and only returns true when $_GET['page'] or $_POST['page'] equals wpr-translate-strings. This lets edits preview immediately without recompiling MO files.
Extension Points
wpr_translate_strings_per_page— int filter controlling the grid page size (default 20).wpr_translate_runtime_translation— filter applied after a runtime lookup; arguments are$replacement, $original, $domain, $language_code, $context.wpr_translate_custom_translations_directory_candidates/wpr_translate_custom_translations_directory— control where generated MO files are written.
Deletion & Reset
- Selected rows —
wpr_translate_admin_handle_delete_strings()decodes base64-JSON payloads submitted viastrings[], validates noncewpr_translate_delete_strings, and calls$wpdb->delete()per(context, name). - Clear all —
wpr_translate_admin_reset_strings()runsDELETE FROM {table}and wipeswpr_translate_scan_stateso the next scan runs in full.
UTF-8 Safety
Language codes and locales are normalized (lowercased, dash-to-underscore) but names, values, and translations are stored verbatim. Do not pass the value or translation through sanitize_title() in extension code — non-Latin characters must be preserved.
Further Reading
- String Scanner — target construction and scan-state cache internals.
- Gettext Pipeline & MO Files — filter hooks and compiled-file layout.
- Automatic Translation — provider wiring and OpenAI request details.
Product information for the plugin is available on the multi-language real estate website page.