Version 1.06.0 – 13 July 2025
Table of Contents
- Overview
- System Requirements
- Quick Start
- Installation
- Configuration Panel
- Language Detection Pipeline
- URL Schema & Routing
- Creating and Managing Content
- REST API & External Translation Workflows
- Widgets, Shortcodes & Templates
- SEO Integration (hreflang)
- Debugging & Logging
- Maintenance & House‑Keeping
- Deactivation & Uninstall
- Troubleshooting Guide
- Appendix A — CLI, Hooks & Filters
1. Overview
Multilingual Engine (ME) is a light‑weight yet enterprise‑ready multilingual framework for WordPress 6.8+.
Key features:
- Language prefixes in URLs (
/en/,/de/, …) — no additional domains. - Ultra‑fast taxonomy‑based language filtering (O(1) DB look‑ups).
- Automatic or on‑demand creation of translation drafts.
- REST‑exposed meta for easy integration with CAT tools / MT services.
- Built‑in
<link rel="alternate" hreflang="…">injection for SEO. - Switcher widget & shortcode, debug/trace mode, PSR‑12 clean code.
2. System Requirements
| Component | Minimum | Recommended |
|---|---|---|
| WordPress core | 6.8 | Latest LTS |
| PHP | 8.3 | 8.3.x |
| Web‑server | Apache/Nginx with mod_rewrite enabled | |
| Database | MySQL 5.7 / MariaDB 10.3 | ↑ with InnoDB |
Note: ME is single‑site only. It neither interferes with, nor supports WordPress Multisite.[^1]
3. Quick Start
- Upload folder
multilingual-engine/withmultilingual-engine.phpto/wp-content/plugins/. - Activate the plugin in Plugins → Installed Plugins.
- Navigate to Settings → General, scroll to Multilingual Engine fields and:
- Enter language codes (e.g.
en,de,fr). - Choose the default language (
en).
- Visit your site — ME redirects you to
/{lang}/…automatically. - Insert
in a menu/widget to let users change language.
4. Installation
4.1 Folder Structure
wp-content/
└── plugins/
└── multilingual-engine/
├── multilingual-engine.php <-- main plugin file
└── languages/ <-- .mo/.po if you localise ME itself
4.2 Activation Sequence
Upon activation ME:
- Registers a non‑public taxonomy
ml_language. - Registers post‑meta
_ml_group_id&_ml_language. - Adds options (with defaults):
ml_available_languages = enml_default_language = enml_auto_create_drafts = 1
The plugin does not touch your database schema.
5. Configuration Panel
(Located in Settings → General)
| Field | Purpose | Validation | Notes |
|---|---|---|---|
| Available languages | Comma‑separated ISO‑639‑1 codes[^2] | trimmed, lowercase, unique | At least one value. |
| Default language | Fallback when detection fails | must be in list above | Applies to first visit & backend. |
| Auto create drafts | On save of a new post/page create empty drafts for every other language | bool | When unchecked, translators are expected to create language records themselves via REST/Admin. |
6. Language Detection Pipeline
The detector selects language in the following priority order:
| # | Source (enum LangSource) | Condition | Example |
|---|---|---|---|
| 1 | URL Prefix /{lang}/ | Path starts with known code | /de/about/ |
| 2 | Cookie ml_lang | Previously chosen by visitor | ml_lang=fr |
| 3 | HTTP Accept‑Language | Two‑letter primary tag matches list | ru-RU,ru;q=0.9,en;q=0.8 |
| 4 | Default Language | Option ml_default_language | en |
The result is cached in memory and (if step 1 fails) persisted in a cookie (1 year, SameSite=Lax).
See §12 for debug tracing of the pipeline.
7. URL Schema & Routing
- Front‑end: Each request must start with a language prefix. Otherwise ME issues an HTTP 302 redirect to the same path under the detected code.[^3]
- Admin: Paths under
/wp-admin/are never redirected or filtered. - Generated links:
translateUrl()replaces or injects a prefix for any absolute URL. - Permalinks: All translations keep identical slugs, only the first segment differs (
/{lang}/post-slug/).
8. Creating and Managing Content
8.1 Original Post Creation
When you publish a post/page in any language:
- Taxonomy
ml_languageis set to the current language. - A unique
_ml_group_idis written. - Optional — drafts for other languages are generated (see §5).
8.2 Editing Translations
- In Posts → All Posts additional quick‑links Edit {CODE} appear for each language in the same group.
- Alternatively filter list by language via Screen Options›Filter by ml_language (WordPress native taxonomy filter UI is not shown because taxonomy is
show_ui =false; use Quick Edit or list views added by ME).
8.3 Manual Draft Creation
If auto‑drafts are disabled create a new post and manually assign:
- Meta
_ml_group_idequal to original group. - Taxonomy term
ml_language = target code.
Easier: POST via REST as shown in §9.
9. REST API & External Translation Workflows
9.1 Exposed Meta Fields
| Key | Type | Access | Description |
|---|---|---|---|
_ml_language | string | read/write | Two‑letter code (en). |
_ml_group_id | string | read/write | Same for all translations of one article. |
POST /wp-json/wp/v2/posts
Authorization: Bearer YOUR_JWT
Content-Type: application/json
{
"title" : "Hallo Welt!",
"content": "<p>…deutsche Version…</p>",
"status" : "publish",
"meta" : {
"_ml_language": "de",
"_ml_group_id": "mlg_64b034b4b7e44.18830524"
}
}
If _ml_group_id is omitted and SEPARATE original is not found, ME treats the post as new original and assigns a fresh group ID.
9.2 Fetching Translations
GET /wp-json/wp/v2/posts?meta_key=_ml_group_id&meta_value=mlg_…&per_page=100
Each record carries _ml_language — pick the desired version.
10. Widgets, Shortcodes & Templates
| Feature | Usage |
|---|---|
| Shortcode | — renders a <select> that reloads with chosen language. |
| Widget | Language Switcher under Appearance → Widgets or Site Editor block list. |
| PHP Helper | MultilingualEngine\Plugin::translateUrl( $absUrl, 'fr', $plugin->langs ). |
You may enqueue your own CSS for .ml-switcher.
11. SEO Integration (hreflang)
For singular posts/pages ME injects, inside <head>, one <link rel="alternate" hreflang="xx" href="…"> per published translation.
No action is needed, though you can hook wp_head priority 1 to prepend/override if desired.
12. Debugging & Logging
| Mode | Enablement | Output |
|---|---|---|
| Trace (recommended) | define('MULTILINGUALENGINE_DEBUG', true); | wp-content/debug.log via error_log() |
| WP core debug | define('WP_DEBUG_LOG', true); | same file |
Trace includes detection source, redirects, draft creation failures, plugin init.
Disable in production to keep headers clean and caches warm.
13. Maintenance & House‑Keeping
13.1 Changing Language List
- Edit Available languages field.
- Save Changes — the taxonomy term list updates immediately.
- New codes: create terms on first use.
- Removed codes: terms remain, but won’t be selectable; clean them with a SQL script if needed.
13.2 Bulk Regenerate Drafts
If you add a new language after many posts already exist and want drafts:
- Temporarily enable Auto create drafts.
- Re‑save originals (bulk “Edit → Update”).
- Disable the option if desired.
13.3 Cleaning Empty Drafts
Run in WP‑CLI:
wp post list --post_status=draft --meta_key=_ml_group_id --format=ids \
| xargs -n1 -I{} wp post delete {} --force
Always backup before bulk deletes.
14. Deactivation & Uninstall
| Action | Effect |
|---|---|
| Deactivate plugin | Front‑end keeps language prefixes → pages become 404. Plan redirects before disabling. |
| Delete plugin | Removes its PHP files only. Options, taxonomy terms & meta stay to preserve data.[^4] |
If you truly want to purge:
delete_option('ml_available_languages');
delete_option('ml_default_language');
delete_option('ml_auto_create_drafts');
Then (optionally) bulk‑delete meta keys and the ml_language taxonomy via direct SQL.
15. Troubleshooting Guide
| Symptom | Likely Cause | Fix |
|---|---|---|
| Endless 302 loop | Cookie set to unsupported code | Clear browser cookies or fix list in Settings (§5). |
| Drafts multiply exponentially | Old plugin version before 1.01.0 caused recursion | Update plugin; delete extra drafts (§13.3). |
| Switcher shows duplicate codes | “Available languages” contains spaces/duplicates | Re‑save list (plugin sanitises input). |
| REST creates post but language filter hides it | _ml_language meta missing or typo | PATCH post via REST with correct meta or edit in Admin. |
hreflang missing | Page has no published translations in same group | Publish at least one other language or set status=publish. |
16. Appendix A — CLI, Hooks & Filters
16.1 CLI Snippets
List all groups with missing languages:
wp eval '
$pl = MultilingualEngine\Plugin::instance();
foreach (get_posts(["meta_key"=>"_ml_group_id","posts_per_page"=>-1,"fields"=>"ids"]) as $id){
$g = get_post_meta($id,"_ml_group_id",true);
$langs = wp_get_post_terms($id,"ml_language",["fields"=>"slugs"]);
if(count($langs)<count($pl->langs)){
echo "$id $g missing\n";
}
}'
16.2 Hooks & Filters
| Hook | Context | Args | Description |
|---|---|---|---|
pre_get_posts (internal) | Front‑end main query | WP_Query | Adds tax_query filter |
save_post (internal) | Any post/page save | ID, WP_Post, bool \$update | Assign language & group, create drafts |
ml_translatable_post_types filter | Early in constructor | array \$defaults | Override list of CPTs subject to translation |
language_switcher shortcode | Any template | — | Renders selector |
Footnotes
[^1]: For multisite consider mapping each language to a separate site, or use Polylang Pro / WPML.
[^2]: Use ISO‑639‑1 primary tags (en, fr, zh). Extended tags (pt-br) are possible but require matching browser headers.
[^3]: Redirect status is 302 (temporary) by default to respect explicit user changes; change to 301 in code if desired.
[^4]: WordPress policy discourages destructive uninstallers; data persistence allows re‑activation without loss.