Full content localization with AI-powered translation, space-level locale management, and intelligent fallback chains.
Numen's i18n system enables content creators to manage multiple language variants of the same content. Every space can have multiple configured locales, and content can be translated automatically using the AI pipeline — preserving the original tone and voice through the existing Persona system.
Key capabilities:
- Space-level locale configuration (add/remove/reorder locales, set default)
- AI-powered translation with tone awareness (respects Persona)
- Translation matrix dashboard (per-content, per-locale coverage)
- Intelligent 5-step fallback chain for graceful degradation
- REST API for translation workflow
- CLI migration tool for existing spaces
Each space has a set of configured locales. Locales are identified by BCP 47 language tags (e.g., en, de, fr, en-US, pt-BR).
Core concepts:
- Default locale — the locale used when no preference is specified
- Supported locales — all locales available in a space
- Locale order — display priority for frontend locale pickers
When a user requests a translation:
- A
TranslateContentJobis queued on theai-pipelinequeue - The job uses the
AITranslationServiceto call the LLM - The prompt includes the Persona configuration to preserve tone
- Translated blocks are stored as
translation_jobsrecords - On completion, a
TranslationCompletedevent fires (webhook-friendly) - On failure, a
TranslationFailedevent fires with error details
When content is requested in a locale that has no translation, Numen applies a 5-step fallback:
1. Exact match (e.g., "en-US" → look for "en-US" translation)
↓ (if missing)
2. Language prefix (e.g., "en-US" → look for "en" translation)
↓ (if missing)
3. Fallback locale (configured in space locale settings)
↓ (if missing)
4. Space default locale
↓ (if missing)
5. "en" (hardcoded fallback)
This ensures content is always available, even with incomplete translations.
For existing spaces, run the setup command to initialize locales:
php artisan numen:setup-i18n {space_id}This:
- Creates a
space_localesrecord for the default locale (en) - Marks it as the default
- Tracks the baseline for future translation jobs
curl -X GET "http://localhost:8000/api/v1/locales?space_id=YOUR_SPACE_ID" \
-H "Authorization: Bearer YOUR_TOKEN"curl -X POST "http://localhost:8000/api/v1/content/{content_id}/translate" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"target_locale": "fr",
"force": false
}'View coverage across all content and locales:
curl -X GET "http://localhost:8000/api/v1/translations/matrix?space_id=YOUR_SPACE_ID&status_filter=all&limit=50" \
-H "Authorization: Bearer YOUR_TOKEN"The SetLocaleFromRequest middleware sets the current locale from (in order of priority):
Accept-Languageheader?locale=query parameterX-Localeheader
When content is requested, the API automatically selects the best-match translation using the fallback chain.
Locale configuration per space.
| Column | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
space_id |
uuid | Foreign key to spaces |
locale |
string | BCP 47 code |
is_default |
boolean | Default locale |
sort_order |
integer | Display priority |
Translation job tracking.
| Column | Type | Notes |
|---|---|---|
id |
uuid | Primary key |
content_id |
uuid | Foreign key |
from_locale |
string | Source locale |
to_locale |
string | Target locale |
status |
enum | pending/completed/failed |
translated_title |
text | Translated title |
translated_excerpt |
text | Translated excerpt |
error_message |
text | Error details |
- REST API Reference — Full OpenAPI spec
- RBAC Guide — Role-based access control
- Taxonomy Guide — Content organization