archetecture security fix
This commit is contained in:
@@ -0,0 +1,739 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user