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() loads wpr_translate_languages and 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 a languages subdirectory. Multisite merges active_sitewide_plugins. Single-file plugins are skipped.
- For each target the scanner computes the latest mtime inside the languages/ directory and short-circuits via wpr_translate_scan_state when nothing has changed and the language signature (md5(wp_json_encode($languages))) matches.
- wpr_translate_admin_collect_strings_from_path() locates .mo/.po files, extracts entries, and hashes them by md5($context . ‘|’ . $value). The name column is populated as str_<md5(value)>.
- wpr_translate_admin_merge_detected_strings() folds overlapping results.
- wpr_translate_admin_persist_detected_strings() looks up existing rows via a chunked SELECT 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 via strings[], validates nonce wpr_translate_delete_strings, and calls $wpdb->delete() per (context, name).
- Clear all — wpr_translate_admin_reset_strings() runs DELETE FROM {table} and wipes wpr_translate_scan_state so 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.