Architecture Overview
This page explains how Omnalingo is put together at a high level. It is written for developers who want to build on top of the plugin, debug unexpected behavior, or understand how the moving parts relate to each other.
Technical Stack
Section titled “Technical Stack”| Layer | Technology |
|---|---|
| PHP backend | PHP 7.4+, strict types throughout |
| Admin frontend | Vue.js 3 + Vite + Pinia (full SPA) |
| UI components | PrimeVue (Aura theme) + Tailwind CSS v3.4 |
| Admin routing | Vue Router, hash mode (#/settings, #/content, etc.) |
| Build | Vite with manifest-based asset loading |
Plugin Structure
Section titled “Plugin Structure”omnalingo/├── config/ Service definitions, language metadata, app config├── src/│ ├── Api/ REST controllers│ ├── Core/ Plugin kernel, DI container│ ├── Repository/ Database access (one class per table group)│ ├── Service/ Business logic│ ├── Subscriber/ WordPress hook registration│ └── Util/ Shared utilities and feature gating├── templates/ PHP templates (editor shell, switcher HTML)├── tests/ PHPUnit test suite└── views/ Vue source (components, stores, composables)The data flow from frontend to database is:
Vue Component → Pinia Store → REST call (auto-prefixed omnalingo/v1/) → REST Controller (validate input, call service) → Service (business logic) → Repository (SQL via $wpdb) → DatabaseFrontend-to-backend writes always go through the REST API. Backend-to-frontend data is injected at page load via window.omnalingoData.
Dependency Injection
Section titled “Dependency Injection”All services are registered in a central container. Classes are resolved on first use and cached as singletons. There is no auto-wiring — every class must be explicitly registered.
Companion plugins can access the container read-only via a WordPress filter:
$container = apply_filters('omnalingo/container', null);if ($container && $container->has(MyTargetClass::class)) { $instance = $container->get(MyTargetClass::class);}The returned wrapper exposes get() and has() only. Extensions cannot replace registered services.
WordPress Hook Registration
Section titled “WordPress Hook Registration”Omnalingo registers all WordPress hooks through subscriber classes. Every subscriber declares its hooks in a static getSubscribedHooks() method, and the plugin’s boot sequence wires them all up at startup. No add_action or add_filter calls are scattered throughout constructors or arbitrary code.
This means: to understand when and why any Omnalingo behavior runs, look at the hook name and priority. The Hooks & Filters reference lists every registered hook.
Virtual URL Layer
Section titled “Virtual URL Layer”Translated pages do not exist as WordPress posts. A URL like /de/about/ is not backed by a German-language copy of the “About” page — it is the same page, served with a language prefix in the URL.
This works in two phases:
Phase 1 (plugins_loaded, priority 1): Omnalingo reads the request URI and detects the language prefix (e.g., de). The detected language is stored for use throughout the rest of the request.
Phase 2 (init, priorities 1–2): The language prefix is stripped from the request URI so WordPress routes to the correct post. Translated URL slugs (Pro feature) are mapped back to their originals.
The locale WordPress filter (priority 0) overrides the active locale so WordPress loads the correct .mo files. Link filters (post_link, page_link, term_link, home_url) add the language prefix back to all generated URLs.
The Pro plugin can hook omnalingo/slug/translate_url to also rewrite slugs themselves — turning /de/about/ into /de/ueber-uns/.
Output Buffer Translation
Section titled “Output Buffer Translation”When a visitor requests a translated page, Omnalingo wraps the entire WordPress output in a buffer. Before the HTML is sent to the browser, the buffer callback finds every original string in the HTML and replaces it with the stored translation.
This approach works because Omnalingo stores translations keyed to the original text. No markup modification or shortcode insertion is needed in your content — translations are applied transparently at output time.
The same buffer mechanism is used in reverse during scanning: Omnalingo renders a page via an internal HTTP request, captures the full HTML output, extracts all translatable strings, and stores them.
Work-While-Polling
Section titled “Work-While-Polling”The gathering, scanning, and translation status endpoints do not just report progress — they also perform work during each request. While the admin page is open and you are watching a progress bar, each status poll processes a batch of work before returning.
Action Scheduler jobs run in the background as a fallback when the browser is not actively polling (e.g., the user navigated away). Concurrency locks prevent both mechanisms from processing the same URL simultaneously.
REST API Surface
Section titled “REST API Surface”All endpoints are under the omnalingo/v1 namespace. Authentication requires administrator capability (manage_options) for all routes. See the REST API reference for the full endpoint list.
The response envelope is consistent across all endpoints:
{ "success": true, "data": { ... } }{ "success": false, "code": "error_code", "message": "Human-readable message" }Extending Omnalingo
Section titled “Extending Omnalingo”The primary extension points are:
| Mechanism | What you can do |
|---|---|
omnalingo/container filter | Access any core service instance |
omnalingo/settings/tabs filter | Add a new settings tab with custom UI |
omnalingo/settings/groups filter | Inject a settings section into an existing tab |
omnalingo/admin/hydration_data filter | Add data to window.omnalingoData |
Custom actions (omnalingo/db/reset, etc.) | React to plugin lifecycle events |
omnalingo/slug/* filters | Customize URL slug behavior (used by Pro) |
See Extending Omnalingo for implementation details and code examples.
Pro Extension
Section titled “Pro Extension”The Pro plugin extends core functionality without modifying core files. It accesses core services via the container filter, registers additional REST routes, adds settings tabs, and hooks the slug filters to apply per-slug URL translations.
Feature gating uses two independent checks:
- Code availability: whether the Pro plugin is active (and at the required version)
- Plan authorization: whether the connected license plan includes a specific feature flag
Both checks are required for Pro-gated features in the UI. The actual enforcement of plan limits (quota, language count) happens on Omnalingo’s cloud, not in the PHP code.
Class Reference
Section titled “Class Reference”The table below lists the main classes by layer. These names are provided as orientation for reading log output or source code — external integrations should use the filter hooks described above rather than instantiating these classes directly.
| Layer | Key classes |
|---|---|
| Kernel | Plugin, Container |
| Subscribers | RewriterSubscriber, TranslationSubscriber, ScannerSubscriber, GettextSubscriber, LanguageSwitcherSubscriber, UrlManagerSubscriber, DatabaseSubscriber, AccessSubscriber, DeactivationSubscriber, AdminSubscriber |
| Services | LocaleService, SettingsService, GatheringService, ScanQueueService, TranslationService, ApiClientService, GettextService, TranslationReplacementService, SlugService, PatternService, AccessService |
| Repositories | UrlRepository, StringRepository, TranslationRepository, PatternRepository, ProgressRepository, MaintenanceRepository |
| Controllers | SettingsController, ContentController, TranslationController, EditorController, GatherController, ScanController, LicenseController, StringListController |