Appearance
Translations (DB-backed i18n with Admin UI) ​
Purpose ​
End-to-end overview of the editable translations system: DB storage, Admin UI, Edge APIs, runtime loading with JSON fallback, caching, stats, and troubleshooting.
Architecture ​
- Storage:
public.i18n_translations(namespace, key, locale, value, status, updated_at) - Meta:
public.i18n_meta(locale, namespace, cache_version, updated_at) bumped by trigger on writes - Security: RLS → public SELECT; admin-only writes via Edge Functions (JWT role)
- APIs: Edge function
translationsfor upsert/patch/import/export/invalidate - Read path: app fetches via supabase-js (public), merges over JSON fallback
- Admin UI:
/admin/translationswith filters, inline edit, import/export, stats
Data Model ​
i18n_translationscolumns:id,namespace,key,locale(en|ro),value,status(draft|published),updated_by,updated_at; unique(namespace, key, locale)- Indexes:
(namespace, key),(locale) - RLS: SELECT true; writes guarded by Edge/JWT role
admin
Edge Endpoints (admin) ​
POST /translations/upsert— upsert row{ namespace, key, locale, value, status? }PATCH /translations/patch/:id— partial updatePOST /translations/invalidate—{ locale, namespace }GET /translations/export— CSV/JSON exportPOST /translations/import— CSV/JSON import (idempotent)
Headers: send authenticated Authorization: Bearer <token>; UI uses supabase.functions.invoke or edgeFetch with x-acqm-role: admin.
Frontend Integration ​
- Loader
src/lib/i18n/loadMessages.ts: DB-first per(locale, namespaces[]), then JSON fallback. DB overrides JSON. - Client
src/lib/i18n/dbClient.ts:fetchTranslations({...}) → { rows, count }(public read, search by namespace/key/value)- Admin actions (upsert/patch/invalidate/import/export) via Edge
- Provider
src/contexts/translation/TranslationProvider.tsx: wires loader intot()utility.
Admin UI ​
Route: /admin/translations
- Tabs: Overview & Stats; Translations Table
- Filters (in Table tab): locale, namespace (full list with pagination), import/export
- Table:
namespace,key,value(editable),value (ro)(side-by-side),status,updated_at - Search: header search across namespace/key/value (EN/RO)
- Footer: "Total: X din Y" (X client filtered, Y exact server count)
Stats ​
- Logic
src/lib/i18n/translationStats.ts: paginated fetch of ALL rows (batches of 1000); compute per-namespaceenCount,roCount,completionRate = ro/en,missingKeys; overall totals. - UI
src/components/admin/translations/TranslationStats.tsx: cards (Total, EN, RO, Overall), complete/incomplete lists. EN/RO cards show completion contextually (no 50% confusion).
Import/Export & Flattening ​
- Import/Export handled by Edge function.
- Keys use dot-notation under namespace (e.g.,
account.documentTypes.id_card.label). - Flatten nested JSON on import; ensure string extraction avoids extra quotes.
Caching & Invalidation ​
- App cache: in-memory map by
(locale, namespace). - DB trigger bumps
i18n_meta.cache_versionon writes. - UI invalidates namespace after edits to refresh immediately.
Security ​
- Public read via RLS; admin writes via Edge (JWT role check). No secrets in frontend; use
.env.localwithVITE_vars.
Performance ​
- Table fetch limited (e.g., 500 rows) for UI speed; server
countshows exact total. - Stats use batched full-table reads to avoid default 1000 cap.
- Indexes support main filters and search.
QA Checklist ​
- Sidebar visible on
/admin/translations; namespaces list complete. - Search matches namespace/key/value (EN/RO).
- Inline edits persist; namespace invalidation refreshes values.
- Stats reflect full DB (thousands), not capped at 1000.
- Footer shows "Total: X din Y" correctly for current filter.
Troubleshooting ​
- Only 1000 rows in stats → ensure paginated fetch in
translationStats. - CORS 401 on Edge → deploy function, allow OPTIONS, send Authorization.
- Quotes in DB values → flatten uses proper string extraction.
- Missing namespaces → ensure paginated namespace fetch (no implicit limit).
Related Files ​
- Schema & RLS:
supabase/migrations/*i18n_translations*.sql - Edge API:
supabase/functions/translations/index.ts - Client:
src/lib/i18n/dbClient.ts - Loader:
src/lib/i18n/loadMessages.ts - Stats:
src/lib/i18n/translationStats.ts - Admin page:
src/pages/admin/translations.tsx - Stats UI:
src/components/admin/translations/TranslationStats.tsx