740 lines
16 KiB
Markdown
740 lines
16 KiB
Markdown
# Notification Language and Localization Policy
|
||
|
||
## 1. Objective
|
||
|
||
The notification system must send all customer-facing notifications in the language selected by the user during account creation.
|
||
|
||
Supported initial languages:
|
||
|
||
```text
|
||
EN
|
||
AR
|
||
FR
|
||
```
|
||
|
||
Language mapping:
|
||
|
||
| User Selection | Locale Code | Notification Language |
|
||
|---|---|---|
|
||
| `EN` | `en` | English |
|
||
| `AR` | `ar` | Arabic |
|
||
| `FR` | `fr` | French |
|
||
|
||
This applies to account creation, workspace creation, invitations, trial notifications, subscription status notifications, billing notifications, invoice notifications, payment failure notifications, refunds, credits, in-app notifications, email notifications, SMS notifications if enabled, and customer-facing webhook labels if applicable.
|
||
|
||
Internal admin alerts may use the internal team’s default language unless configured otherwise.
|
||
|
||
---
|
||
|
||
## 2. Source of Truth for Notification Language
|
||
|
||
The user’s selected language at account creation must be stored and used as the primary language preference.
|
||
|
||
### Required User Field
|
||
|
||
```sql
|
||
ALTER TABLE users
|
||
ADD COLUMN preferred_language TEXT NOT NULL DEFAULT 'en';
|
||
```
|
||
|
||
Allowed values:
|
||
|
||
```text
|
||
en
|
||
ar
|
||
fr
|
||
```
|
||
|
||
### Account Creation Requirement
|
||
|
||
During account creation, the user must select or confirm a language.
|
||
|
||
Example payload:
|
||
|
||
```json
|
||
{
|
||
"email": "user@example.com",
|
||
"name": "Example User",
|
||
"preferred_language": "ar"
|
||
}
|
||
```
|
||
|
||
Rules:
|
||
|
||
- `preferred_language` is required at account creation.
|
||
- If not provided, default to `en`.
|
||
- The selected language must be saved on the user profile.
|
||
- The selected language must be used for all customer-facing notifications.
|
||
- Users may update their preferred language later from account settings.
|
||
- Updating the language affects future notifications only, not historical notifications.
|
||
|
||
---
|
||
|
||
## 3. Language Resolution Policy
|
||
|
||
When sending a notification, resolve the language in this order:
|
||
|
||
```text
|
||
recipient user preferred_language
|
||
workspace default_language
|
||
billing account preferred_language
|
||
organization default_language
|
||
system default_language
|
||
```
|
||
|
||
Default:
|
||
|
||
```text
|
||
en
|
||
```
|
||
|
||
Example:
|
||
|
||
```text
|
||
User preferred_language = ar
|
||
Workspace default_language = fr
|
||
System default_language = en
|
||
|
||
Selected notification language = ar
|
||
```
|
||
|
||
The recipient’s own language wins. Not the workspace. Not the billing account. Not a hardcoded backend default.
|
||
|
||
---
|
||
|
||
## 4. Workspace and Billing Language Fields
|
||
|
||
In addition to the user’s language, store default language on workspace and billing account.
|
||
|
||
### Workspace Field
|
||
|
||
```sql
|
||
ALTER TABLE workspaces
|
||
ADD COLUMN default_language TEXT NOT NULL DEFAULT 'en';
|
||
```
|
||
|
||
### Billing Account Field
|
||
|
||
```sql
|
||
ALTER TABLE billing_accounts
|
||
ADD COLUMN preferred_language TEXT NOT NULL DEFAULT 'en';
|
||
```
|
||
|
||
Purpose:
|
||
|
||
| Field | Purpose |
|
||
|---|---|
|
||
| `users.preferred_language` | Primary language for direct user notifications |
|
||
| `workspaces.default_language` | Fallback for workspace-level notifications |
|
||
| `billing_accounts.preferred_language` | Fallback for finance/billing notifications |
|
||
| `system.default_language` | Final fallback |
|
||
|
||
---
|
||
|
||
## 5. Template Localization Policy
|
||
|
||
Every customer-facing notification template must exist in all supported languages.
|
||
|
||
Required locales:
|
||
|
||
```text
|
||
en
|
||
ar
|
||
fr
|
||
```
|
||
|
||
### Template Table Update
|
||
|
||
```sql
|
||
CREATE TABLE notification_templates (
|
||
id UUID PRIMARY KEY,
|
||
|
||
template_key TEXT NOT NULL,
|
||
category TEXT NOT NULL,
|
||
channel TEXT NOT NULL,
|
||
|
||
locale TEXT NOT NULL,
|
||
|
||
subject TEXT,
|
||
body TEXT NOT NULL,
|
||
|
||
required_variables JSONB,
|
||
optional_variables JSONB,
|
||
|
||
version INTEGER NOT NULL DEFAULT 1,
|
||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||
|
||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||
|
||
UNIQUE(template_key, channel, locale, version)
|
||
);
|
||
```
|
||
|
||
Preferred template structure:
|
||
|
||
```text
|
||
template_key = account.created.email
|
||
locale = en
|
||
|
||
template_key = account.created.email
|
||
locale = ar
|
||
|
||
template_key = account.created.email
|
||
locale = fr
|
||
```
|
||
|
||
Do not encode the locale into the template key if the table already has a `locale` column. Duplicating state is how small mistakes become expensive folklore.
|
||
|
||
---
|
||
|
||
## 6. Template Lookup Policy
|
||
|
||
When rendering a notification:
|
||
|
||
```text
|
||
1. Resolve recipient language.
|
||
2. Find active template for template_key + channel + resolved locale.
|
||
3. If unavailable, fall back to English.
|
||
4. If English template is unavailable, fail notification creation and alert admins.
|
||
```
|
||
|
||
Lookup flow:
|
||
|
||
```text
|
||
event received
|
||
↓
|
||
resolve recipient
|
||
↓
|
||
resolve recipient language
|
||
↓
|
||
find localized template
|
||
↓
|
||
render template
|
||
↓
|
||
send notification
|
||
```
|
||
|
||
Fallback rules:
|
||
|
||
| Condition | Action |
|
||
|---|---|
|
||
| `ar` template exists | Send Arabic notification |
|
||
| `fr` template exists | Send French notification |
|
||
| Requested locale missing | Fall back to English |
|
||
| English fallback missing | Fail and alert internal admins |
|
||
| Template variables missing | Fail and alert internal admins |
|
||
|
||
Fallback to English is allowed only as a safety net. It should be monitored as a product defect.
|
||
|
||
---
|
||
|
||
## 7. Arabic Language Requirements
|
||
|
||
Arabic notifications must support right-to-left rendering.
|
||
|
||
For locale:
|
||
|
||
```text
|
||
ar
|
||
```
|
||
|
||
Apply:
|
||
|
||
```html
|
||
<html lang="ar" dir="rtl">
|
||
```
|
||
|
||
or for partial rendering:
|
||
|
||
```html
|
||
<div lang="ar" dir="rtl">
|
||
...
|
||
</div>
|
||
```
|
||
|
||
Arabic requirements:
|
||
|
||
- Use RTL layout for email and in-app notifications.
|
||
- Align Arabic text to the right.
|
||
- Use Arabic translations for all customer-facing labels.
|
||
- Use localized date formatting where possible.
|
||
- Keep invoice numbers, amounts, and technical IDs readable.
|
||
- Avoid mixing English UI labels into Arabic notifications unless they are brand or product names.
|
||
- Test Arabic templates separately.
|
||
|
||
---
|
||
|
||
## 8. French Language Requirements
|
||
|
||
For locale:
|
||
|
||
```text
|
||
fr
|
||
```
|
||
|
||
Apply:
|
||
|
||
```html
|
||
<html lang="fr">
|
||
```
|
||
|
||
French requirements:
|
||
|
||
- Use French templates for all customer-facing messages.
|
||
- Use localized date formatting.
|
||
- Use localized money formatting where appropriate.
|
||
- Avoid partial English/French mixed messages.
|
||
- Keep plan names and product names unchanged unless officially translated.
|
||
|
||
---
|
||
|
||
## 9. English Language Requirements
|
||
|
||
For locale:
|
||
|
||
```text
|
||
en
|
||
```
|
||
|
||
Apply:
|
||
|
||
```html
|
||
<html lang="en">
|
||
```
|
||
|
||
English is the default fallback language.
|
||
|
||
---
|
||
|
||
## 10. Localized Formatting Policy
|
||
|
||
Notifications should localize:
|
||
|
||
- Subject lines
|
||
- Body text
|
||
- Button labels
|
||
- Status labels
|
||
- Dates
|
||
- Currency display
|
||
- Invoice labels
|
||
- Subscription status labels
|
||
- Error messages
|
||
- Call-to-action text
|
||
|
||
Date formatting examples:
|
||
|
||
| Locale | Display |
|
||
|---|---|
|
||
| `en` | June 15, 2026 |
|
||
| `fr` | 15 juin 2026 |
|
||
| `ar` | ١٥ يونيو ٢٠٢٦ |
|
||
|
||
Currency formatting should respect both billing currency and locale.
|
||
|
||
Example for USD:
|
||
|
||
| Locale | Display |
|
||
|---|---|
|
||
| `en` | $1,200.00 |
|
||
| `fr` | 1 200,00 $US |
|
||
| `ar` | ١٬٢٠٠٫٠٠ US$ |
|
||
|
||
Do not store localized amounts as the source of truth. Store money as integer minor units and format at render time.
|
||
|
||
---
|
||
|
||
## 11. Localized Subscription Status Labels
|
||
|
||
### English
|
||
|
||
| Internal Status | English Label |
|
||
|---|---|
|
||
| `trialing` | Trial active |
|
||
| `active` | Active |
|
||
| `payment_pending` | Payment pending |
|
||
| `past_due` | Payment overdue |
|
||
| `suspended` | Suspended |
|
||
| `canceled` | Canceled |
|
||
| `expired` | Expired |
|
||
|
||
### French
|
||
|
||
| Internal Status | French Label |
|
||
|---|---|
|
||
| `trialing` | Essai actif |
|
||
| `active` | Actif |
|
||
| `payment_pending` | Paiement en attente |
|
||
| `past_due` | Paiement en retard |
|
||
| `suspended` | Suspendu |
|
||
| `canceled` | Annulé |
|
||
| `expired` | Expiré |
|
||
|
||
### Arabic
|
||
|
||
| Internal Status | Arabic Label |
|
||
|---|---|
|
||
| `trialing` | الفترة التجريبية نشطة |
|
||
| `active` | نشط |
|
||
| `payment_pending` | الدفع قيد الانتظار |
|
||
| `past_due` | الدفع متأخر |
|
||
| `suspended` | معلّق |
|
||
| `canceled` | ملغى |
|
||
| `expired` | منتهي |
|
||
|
||
---
|
||
|
||
## 12. Localized Invoice Labels
|
||
|
||
### English
|
||
|
||
| Internal Invoice Status | English Label |
|
||
|---|---|
|
||
| `draft` | Preparing |
|
||
| `open` | Due |
|
||
| `payment_pending` | Payment processing |
|
||
| `paid` | Paid |
|
||
| `partially_paid` | Partially paid |
|
||
| `past_due` | Past due |
|
||
| `void` | Canceled |
|
||
| `uncollectible` | Contact support |
|
||
| `refunded` | Refunded |
|
||
| `partially_refunded` | Partially refunded |
|
||
|
||
### French
|
||
|
||
| Internal Invoice Status | French Label |
|
||
|---|---|
|
||
| `draft` | En préparation |
|
||
| `open` | À payer |
|
||
| `payment_pending` | Paiement en cours |
|
||
| `paid` | Payée |
|
||
| `partially_paid` | Partiellement payée |
|
||
| `past_due` | En retard |
|
||
| `void` | Annulée |
|
||
| `uncollectible` | Contacter le support |
|
||
| `refunded` | Remboursée |
|
||
| `partially_refunded` | Partiellement remboursée |
|
||
|
||
### Arabic
|
||
|
||
| Internal Invoice Status | Arabic Label |
|
||
|---|---|
|
||
| `draft` | قيد الإعداد |
|
||
| `open` | مستحقة الدفع |
|
||
| `payment_pending` | الدفع قيد المعالجة |
|
||
| `paid` | مدفوعة |
|
||
| `partially_paid` | مدفوعة جزئياً |
|
||
| `past_due` | متأخرة |
|
||
| `void` | ملغاة |
|
||
| `uncollectible` | تواصل مع الدعم |
|
||
| `refunded` | مستردة |
|
||
| `partially_refunded` | مستردة جزئياً |
|
||
|
||
---
|
||
|
||
## 13. Notification Creation Logic
|
||
|
||
When an event creates a notification, include the resolved locale.
|
||
|
||
### Notification Record Update
|
||
|
||
```sql
|
||
ALTER TABLE notifications
|
||
ADD COLUMN locale TEXT NOT NULL DEFAULT 'en';
|
||
```
|
||
|
||
Example notification record:
|
||
|
||
```json
|
||
{
|
||
"event_type": "invoice.finalized",
|
||
"recipient_email": "finance@example.com",
|
||
"recipient_role": "billing_owner",
|
||
"channel": "email",
|
||
"template_key": "invoice.finalized.email",
|
||
"locale": "fr",
|
||
"status": "pending"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 14. Notification Rendering Pseudocode
|
||
|
||
```ts
|
||
type SupportedLocale = "en" | "ar" | "fr";
|
||
|
||
function resolveNotificationLocale(recipient, workspace, billingAccount): SupportedLocale {
|
||
return (
|
||
recipient.preferredLanguage ||
|
||
workspace.defaultLanguage ||
|
||
billingAccount.preferredLanguage ||
|
||
"en"
|
||
);
|
||
}
|
||
|
||
async function createNotification(event, recipient) {
|
||
const locale = resolveNotificationLocale(
|
||
recipient.user,
|
||
event.workspace,
|
||
event.billingAccount
|
||
);
|
||
|
||
const template = await findTemplate({
|
||
templateKey: event.templateKey,
|
||
channel: event.channel,
|
||
locale
|
||
});
|
||
|
||
const fallbackTemplate = await findTemplate({
|
||
templateKey: event.templateKey,
|
||
channel: event.channel,
|
||
locale: "en"
|
||
});
|
||
|
||
const selectedTemplate = template || fallbackTemplate;
|
||
|
||
if (!selectedTemplate) {
|
||
throw new Error("Missing notification template");
|
||
}
|
||
|
||
return {
|
||
eventType: event.type,
|
||
recipientEmail: recipient.email,
|
||
channel: event.channel,
|
||
templateKey: event.templateKey,
|
||
locale: selectedTemplate.locale,
|
||
renderedContent: renderTemplate(selectedTemplate, event.payload)
|
||
};
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 15. Account Creation Flow Update
|
||
|
||
During account creation:
|
||
|
||
```text
|
||
user selects language
|
||
↓
|
||
system stores preferred_language
|
||
↓
|
||
account.created event emitted
|
||
↓
|
||
notification system resolves preferred_language
|
||
↓
|
||
localized welcome notification sent
|
||
```
|
||
|
||
### Account Creation API Update
|
||
|
||
```http
|
||
POST /accounts
|
||
```
|
||
|
||
Request:
|
||
|
||
```json
|
||
{
|
||
"name": "Example User",
|
||
"email": "user@example.com",
|
||
"password": "secure_password",
|
||
"preferred_language": "ar"
|
||
}
|
||
```
|
||
|
||
Response:
|
||
|
||
```json
|
||
{
|
||
"account_id": "acct_123",
|
||
"user_id": "user_123",
|
||
"preferred_language": "ar"
|
||
}
|
||
```
|
||
|
||
Validation:
|
||
|
||
```text
|
||
preferred_language must be one of: en, ar, fr
|
||
```
|
||
|
||
---
|
||
|
||
## 16. Notification Preference Update
|
||
|
||
Language preference should be separate from notification category preferences.
|
||
|
||
Example user settings:
|
||
|
||
```text
|
||
preferred_language = ar
|
||
invoice.email.enabled = true
|
||
billing.email.enabled = true
|
||
onboarding.email.enabled = false
|
||
```
|
||
|
||
Rules:
|
||
|
||
- Language controls the language of notifications.
|
||
- Preferences control whether allowed notifications are sent.
|
||
- Critical billing, invoice, payment, and security notifications still follow mandatory delivery rules.
|
||
- Changing language does not unsubscribe the user from notifications.
|
||
- Changing language affects future notifications only.
|
||
|
||
---
|
||
|
||
## 17. Testing Requirements
|
||
|
||
### Unit Tests
|
||
|
||
Test:
|
||
|
||
```text
|
||
User with preferred_language=en receives English template
|
||
User with preferred_language=ar receives Arabic template
|
||
User with preferred_language=fr receives French template
|
||
Missing Arabic template falls back to English
|
||
Missing French template falls back to English
|
||
Missing English fallback fails notification creation
|
||
Arabic email renders with dir="rtl"
|
||
French email renders with lang="fr"
|
||
English email renders with lang="en"
|
||
```
|
||
|
||
### Integration Tests
|
||
|
||
Test:
|
||
|
||
```text
|
||
Account created with EN → welcome email sent in English
|
||
Account created with AR → welcome email sent in Arabic
|
||
Account created with FR → welcome email sent in French
|
||
Invoice finalized for AR billing owner → Arabic invoice notification
|
||
Payment failed for FR billing owner → French payment failure notification
|
||
Subscription suspended for EN admin → English suspension notification
|
||
```
|
||
|
||
### End-to-End Tests
|
||
|
||
Test:
|
||
|
||
```text
|
||
User creates account and selects Arabic
|
||
↓
|
||
Arabic welcome email is sent
|
||
↓
|
||
User starts trial
|
||
↓
|
||
Arabic trial notification is sent
|
||
↓
|
||
Invoice is generated
|
||
↓
|
||
Arabic invoice notification is sent
|
||
↓
|
||
Payment fails
|
||
↓
|
||
Arabic payment failure notification is sent
|
||
```
|
||
|
||
---
|
||
|
||
## 18. Monitoring Requirements
|
||
|
||
Track localization metrics:
|
||
|
||
```text
|
||
notifications_sent_by_locale
|
||
notifications_failed_by_locale
|
||
template_missing_by_locale
|
||
fallback_to_english_count
|
||
rtl_rendering_test_failures
|
||
language_preference_update_count
|
||
```
|
||
|
||
Alert on:
|
||
|
||
```text
|
||
Missing Arabic template
|
||
Missing French template
|
||
Fallback-to-English spike
|
||
Arabic rendering failure
|
||
Localized template variable error
|
||
```
|
||
|
||
Fallback-to-English should be treated as a defect, not a harmless convenience.
|
||
|
||
---
|
||
|
||
## 19. Updated Notification Localization Section
|
||
|
||
Replace the earlier notification localization section with this stricter version.
|
||
|
||
### Notification Localization
|
||
|
||
Notification templates must support the user’s selected account language.
|
||
|
||
Supported locales:
|
||
|
||
```text
|
||
en
|
||
ar
|
||
fr
|
||
```
|
||
|
||
Template lookup order:
|
||
|
||
```text
|
||
recipient user preferred_language
|
||
workspace default_language
|
||
billing account preferred_language
|
||
system default_language
|
||
```
|
||
|
||
The language selected during account creation is the primary source of truth for user-facing notifications.
|
||
|
||
Arabic notifications must render in RTL mode.
|
||
|
||
French and English notifications must use localized date, currency, and status labels.
|
||
|
||
Fallback to English is allowed only when a localized template is missing, and every fallback must create an internal alert.
|
||
|
||
---
|
||
|
||
## 20. Final Localization Policy
|
||
|
||
Use this as the baseline rule:
|
||
|
||
```text
|
||
The language selected by the user during account creation is the primary language for all customer-facing notifications.
|
||
Supported languages are English, Arabic, and French.
|
||
English uses locale en.
|
||
Arabic uses locale ar and must render right-to-left.
|
||
French uses locale fr.
|
||
Each customer-facing notification template must exist in en, ar, and fr.
|
||
If a localized template is missing, the system may fall back to English, but must log and alert the missing localization.
|
||
Billing, invoice, subscription, and account notifications must use the recipient’s preferred language.
|
||
Language preference is separate from notification opt-in preferences.
|
||
Changing language affects future notifications only.
|
||
Internal admin alerts may use the internal default language unless configured otherwise.
|
||
```
|
||
|
||
---
|
||
|
||
## 21. Definition of Done
|
||
|
||
The notification localization system is complete when:
|
||
|
||
- Account creation stores `preferred_language`.
|
||
- Supported languages are limited to `en`, `ar`, and `fr`.
|
||
- Notification records store the resolved locale.
|
||
- Email templates exist in English, Arabic, and French.
|
||
- In-app notification templates exist in English, Arabic, and French.
|
||
- Arabic templates render correctly in RTL.
|
||
- French templates render with French labels and formatting.
|
||
- Dates and currency are localized during rendering.
|
||
- Missing localized templates fall back to English and create alerts.
|
||
- Tests cover account, subscription, billing, invoice, and payment notifications for all supported languages.
|