per-tenant-locales
"""Per-Tenant Locales and Translation Overrides
This guide explains how tenants can customize their language preferences and override system translations to match their specific terminology, industry language, or brand voice.
Overview
Framework M supports tenant-specific localization through two mechanisms:
- Tenant Default Locale: Set organization-wide language preference
- Tenant Translation Overrides: Customize specific translations
Setting Tenant Default Locale
Backend: Configure Tenant
Set the default locale in the tenant's attributes:
from framework_m.core.interfaces.tenant import TenantContext
# Configure tenant with Hindi as default locale
tenant = TenantContext(
tenant_id="acme-corp",
attributes={
"default_locale": "hi", # Hindi
"plan": "enterprise",
"features": ["advanced_reports"]
}
)
Locale Resolution Priority
When a user makes a request, the locale is resolved in this order:
- Accept-Language header (browser preference)
- User.locale field (personal preference)
- Tenant default_locale (organization default) ← Tenant setting
- System DEFAULT_LOCALE (fallback to "en")
Example: Healthcare Organization
# Healthcare tenant defaults to Tamil
healthcare_tenant = TenantContext(
tenant_id="healthcare-india",
attributes={
"default_locale": "ta", # Tamil
"industry": "healthcare"
}
)
# All users in this tenant see Tamil by default
# unless they:
# - Set their browser to a different language
# - Set their personal locale preference
Tenant Translation Overrides
Tenants can provide custom translations to override system translations. This is useful for:
- Industry-specific terminology
- Brand-specific wording
- Localized company names in messages
Creating Tenant Translations
Option 1: Via API
# Create tenant-specific translation
POST /api/v1/TenantTranslation
Authorization: Bearer <token>
X-Tenant-ID: acme-corp
{
"tenant_id": "acme-corp",
"source_text": "Customer",
"translated_text": "Patient",
"locale": "en",
"context": "field_label"
}
Option 2: Via Python Code
from framework_m.core.doctypes.tenant_translation import TenantTranslation
# Healthcare tenant uses "Patient" instead of "Customer"
patient_translation = TenantTranslation(
tenant_id="healthcare-india",
source_text="Customer",
translated_text="Patient",
locale="en",
context="field_label"
)
# Same in Tamil
patient_translation_ta = TenantTranslation(
tenant_id="healthcare-india",
source_text="Customer",
translated_text="நோயாளி", # Patient in Tamil
locale="ta",
context="field_label"
)
Translation Priority
When translating text, the system checks in this order:
- TenantTranslation (tenant-specific override)
- Translation (system-wide translation)
- Default parameter (if provided)
- Source text (original text)
Example: Retail vs Healthcare
# System translation (default)
Translation(
source_text="Customer",
translated_text="ग्राहक", # Customer in Hindi
locale="hi",
context="field_label"
)
# Retail tenant override (uses "Client")
TenantTranslation(
tenant_id="retail-corp",
source_text="Customer",
translated_text="क्लाइंट", # Client in Hindi
locale="hi",
context="field_label"
)
# Healthcare tenant override (uses "Patient")
TenantTranslation(
tenant_id="healthcare-india",
source_text="Customer",
translated_text="रोगी", # Patient in Hindi
locale="hi",
context="field_label"
)
Result
When each tenant's users see the "Customer" field:
- Retail tenant: "क्लाइंट" (Client)
- Healthcare tenant: "रोगी" (Patient)
- Other tenants: "ग्राहक" (Customer - system default)
Managing Tenant Translations
List Tenant Translations
# Get all translations for a tenant
GET /api/v1/TenantTranslation?filters=[{"field":"tenant_id","operator":"eq","value":"acme-corp"}]
Authorization: Bearer <token>
X-Tenant-ID: acme-corp
Update Tenant Translation
# Update existing translation
PUT /api/v1/TenantTranslation/{id}
Authorization: Bearer <token>
X-Tenant-ID: acme-corp
{
"translated_text": "Updated translation"
}
Delete Tenant Translation
# Remove custom translation (falls back to system translation)
DELETE /api/v1/TenantTranslation/{id}
Authorization: Bearer <token>
X-Tenant-ID: acme-corp
Row-Level Security
TenantTranslation DocType has RLS enabled:
class TenantTranslation(BaseDocType):
class Meta:
apply_rls = True
rls_field = "tenant_id"
This ensures:
- Tenants can only see/modify their own translations
- System automatically filters by tenant_id
- No cross-tenant data leakage
Frontend Usage
Automatic Translation
Field labels in forms are automatically translated based on:
- User's resolved locale
- Tenant overrides (if any)
// Frontend fetches metadata
const meta = await fetch('/api/meta/Invoice', {
headers: {
'X-Tenant-ID': 'healthcare-india',
'Accept-Language': 'ta'
}
});
// Response includes translated field labels
{
"doctype": "Invoice",
"locale": "ta",
"schema": {
"properties": {
"customer": {
"description": "நோயாளி", // "Patient" (tenant override)
"type": "string"
}
}
}
}
Translation Context
Use context to disambiguate translations:
# Button label
TenantTranslation(
tenant_id="acme-corp",
source_text="Save",
translated_text="सुरक्षित करें", # "Secure/Protect" emphasis
locale="hi",
context="button"
)
# Field label
TenantTranslation(
tenant_id="acme-corp",
source_text="Save",
translated_text="बचत", # "Savings" (financial context)
locale="hi",
context="field_label"
)
Best Practices
1. Start with System Translations
Only create tenant overrides when necessary:
# Good: Override for industry-specific term
TenantTranslation(
tenant_id="medical-corp",
source_text="Customer",
translated_text="Patient",
locale="en"
)
# Avoid: Duplicating system translations unnecessarily
# (use system Translation instead)
2. Use Consistent Context
# Consistent context for all field labels
context="field_label"
# Consistent context for all buttons
context="button"
# Consistent context for all messages
context="message"
3. Document Tenant Customizations
Keep a record of why overrides exist:
# Document the reason in comments or separate docs
TenantTranslation(
tenant_id="healthcare-india",
source_text="Total Amount",
translated_text="மொத்த கட்டணம்", # "Total Fee" per healthcare terminology
locale="ta",
context="field_label"
)
4. Test in Multiple Locales
# Test each tenant's locale
curl -H "X-Tenant-ID: healthcare-india" \
-H "Accept-Language: ta" \
/api/meta/Invoice
curl -H "X-Tenant-ID: retail-corp" \
-H "Accept-Language: hi" \
/api/meta/Invoice
Migration from Single-Tenant
If migrating from single-tenant to multi-tenant:
- Review existing translations: Decide which are tenant-specific
- Create tenant translations: Move tenant-specific translations to TenantTranslation
- Keep system translations: Keep common translations in Translation
- Test thoroughly: Verify each tenant sees correct translations
See Also
framework_m.core.doctypes.tenant_translation- TenantTranslation DocTypeframework_m.adapters.i18n.DefaultI18nAdapter- Translation adapter with tenant supportframework_m.adapters.web.middleware.locale- Locale resolution middlewaredocs/developer/locale-resolution.md- Complete locale resolution guide