Skip to content

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.


LayerTechnology
PHP backendPHP 7.4+, strict types throughout
Admin frontendVue.js 3 + Vite + Pinia (full SPA)
UI componentsPrimeVue (Aura theme) + Tailwind CSS v3.4
Admin routingVue Router, hash mode (#/settings, #/content, etc.)
BuildVite with manifest-based asset loading

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)
→ Database

Frontend-to-backend writes always go through the REST API. Backend-to-frontend data is injected at page load via window.omnalingoData.


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.


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.


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/.


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.


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.


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" }

The primary extension points are:

MechanismWhat you can do
omnalingo/container filterAccess any core service instance
omnalingo/settings/tabs filterAdd a new settings tab with custom UI
omnalingo/settings/groups filterInject a settings section into an existing tab
omnalingo/admin/hydration_data filterAdd data to window.omnalingoData
Custom actions (omnalingo/db/reset, etc.)React to plugin lifecycle events
omnalingo/slug/* filtersCustomize URL slug behavior (used by Pro)

See Extending Omnalingo for implementation details and code examples.


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.


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.

LayerKey classes
KernelPlugin, Container
SubscribersRewriterSubscriber, TranslationSubscriber, ScannerSubscriber, GettextSubscriber, LanguageSwitcherSubscriber, UrlManagerSubscriber, DatabaseSubscriber, AccessSubscriber, DeactivationSubscriber, AdminSubscriber
ServicesLocaleService, SettingsService, GatheringService, ScanQueueService, TranslationService, ApiClientService, GettextService, TranslationReplacementService, SlugService, PatternService, AccessService
RepositoriesUrlRepository, StringRepository, TranslationRepository, PatternRepository, ProgressRepository, MaintenanceRepository
ControllersSettingsController, ContentController, TranslationController, EditorController, GatherController, ScanController, LicenseController, StringListController