0%
balqalam Logo
balqalam
FeaturesCommunityPricingDocumentation
Login
  • Introduction
  • Help center
  • Common questions
  • Get Started
  • Localhost
  • Architecture
  • Structure
  • Pattern
  • Page
  • Layout
  • Content
  • Types
  • Config
  • Actions
  • Queries
  • Authorization
  • Validation
  • Form
  • Table
  • Detail
  • Card
  • Util
  • Hooks
  • List Params
  • Views
  • README.md
  • ISSUE.md
  • Technology Stack
  • Database
  • File
  • CDN Assets
  • Entry Points
  • Dashboard
  • Authentication
  • Credentials
  • OAuth
  • Flow Diagrams
  • Multi-Tenancy
  • Offline
  • Onboarding
  • Onboarding Videos
  • Add Values
  • Admission
  • Application
  • Attendance
  • Compliance
  • Profile
  • Exams
  • Exam Wizard
  • Timetable
  • Classrooms
  • Notifications
  • Conference
  • LMS (Lumos)
  • Finance
  • Fee Management
  • Invoice
  • Wallet
  • Salary
  • Payroll
  • Timesheet
  • Expenses
  • Budget
  • Receipt
  • Accounts
  • Banking
  • Reports
  • Dashboard
  • Permissions
  • Messages
  • Integration Flow
  • Provision
  • AI Document Processing
  • Document Intelligence
  • Internationalization
  • Translation
  • Translation Guide
  • Icons
  • Docs Factory
  • Inspiration
  • Listings
  • Teachers
  • Students
  • Catalog
  • Library
  • Contributing
  • Code of conduct
  • GitHub Workflow
  • Database Seeds
  • Database Safety
  • Test Accounts
  • Bulk import
  • Playwright
  • Prettier
  • Block Rebound
  • Transportation

Translation

PreviousNext

Two-tier i18n — dictionary JSON for static UI, batched localize() for dynamic content. Coverage gaps and adoption patterns.

Translation runs on two tiers: dictionary JSON files for static UI (parity-enforced across ar/en by a CI test) and the batched localize() engine for dynamic database content via a three-tier cache (in-memory LRU → Translation table → provider chain). The provider chain (engine.ts) tries Google Translate first and falls back to a Groq LLM (free tier) — each behind a circuit breaker — so a dead provider (as happened 2026-06-14 → 2026-07-12 when the Google quota died and silently killed all dynamic translation) can never again take translations down platform-wide. A registry-driven sweep (pnpm i18n:backfill + the daily /api/cron/translation-sweep) backfills and self-heals the cache for existing content, person names, new schools, and bulk imports. Drift can no longer ship: en/ar key parity, untranslated placeholders, hardcoded-string counts, and RTL physical classes are all gated in pnpm test and CI. The remaining work is the hardcoded-string sweep tracked by the ratchet baselines — run npx tsx scripts/i18n-hardcoded-ratchet.ts --by-dir for the live count.

Architecture

TierWhatHow
1 — Static UISidebar, table headers, status badges, labelsDictionary JSON → getDictionary() → dictionary.key
2 — DynamicAnnouncements, subjects, departments, eventsSingle-language storage → batched localize()/getLabels() → LRU → DB cache → Google → Groq (LLM)

Infrastructure files

FilePurpose
src/components/translation/localize.tsBatched localize()/localizeOne() — preferred
src/components/translation/registry.tsTRANSLATABLE map (model → fields)
src/components/translation/prewarm.tsCache-fill on write via after()
src/components/translation/person.tsgetNames()/getLabels() — batched names/labels
src/components/translation/display.tsgetText(), getFields() — single values only
src/components/translation/actions.tstranslate() core (LRU → DB → provider chain)
src/components/translation/engine.tsProvider chain: Google → Groq, circuit breakers
src/components/translation/google.tsGoogle client: timeout, chunking, retry policy
src/components/translation/groq.tsGroq LLM fallback: JSON batch protocol, free tier
src/components/translation/sweep.tsCache sweep core (backfill CLI + daily cron)
src/components/translation/util.tswithLang, detectScript
src/components/internationalization/helpers/index.tsValidationHelper, ToastHelper, ErrorHelper
src/components/internationalization/namespaces.tsSingle namespace registry (server + client)
src/components/internationalization/dictionaries.tsRoute-scoped dictionary loaders (cache()-wrapped)

Coverage gaps

GapStatus
Button text (Save, Cancel, Delete…)Keys exist in dictionary.common.*
Validation (Zod messages)Needs schema-factory refactor
Config / select optionsNeeds option factories
Form labels and placeholdersPartial adoption
Server action errorsNeeds error-code pattern
Wizard tab / group labelsPending
Empty statesPending
Toast messagesNeeds ToastHelper plumbing

Cards and Lab sidebar items also lack dictionary keys. Run /i18n-check for the current per-area count.

Priority order

  1. Button text — keys already exist in dictionary.common.*.
  2. Wizard group labels — high visibility.
  3. Form labels — largest UX impact.
  4. Select options — needs config factory.
  5. Validation — needs schema factory refactor.
  6. Server action errors — needs error code pattern.
  7. Empty states.
  8. Toast messages — needs dictionary plumbing.
  9. getText expansion to remaining content files.

Form pattern

Pass dictionary slices into client forms.

// content.tsx
const dictionary = await getDictionary(lang)
return <StudentForm dictionary={dictionary.school.students} />
 
// form.tsx
<FormLabel>{dictionary.form.name}</FormLabel>
<Input placeholder={dictionary.form.namePlaceholder} {...field} />

Select pattern

Export option factories, not static arrays.

export const getGenderOptions = (d: Dictionary["school"]) => [
  { value: "male", label: d.students.gender.male },
  { value: "female", label: d.students.gender.female },
]

Validation pattern

Schemas become factories that take a ValidationHelper. Already shipping in onboarding and auth.

import { getValidationMessages } from "@/components/internationalization/helpers"
 
export const createStudentSchema = (
  v: ReturnType<typeof getValidationMessages>
) =>
  z.object({
    name: z.string().min(1, v.required()),
    email: z.string().email(v.email()),
    phone: z.string().min(10, v.minLength(10)),
  })

Blocker: validation files across the dashboard need to convert from module-level schemas to factories.

Toast pattern

import { getToastMessages } from "@/components/internationalization/helpers"
 
const t = useMemo(() => getToastMessages(dictionary), [dictionary])
 
if (result.success) toast.success(t.success.student.created())
else toast.error(t.error.student.createFailed())

Blocker: toasts run client-side; ToastHelper needs dictionary in scope.

Server action error pattern

Return error codes; translate on the client.

// actions.ts
if (!session?.user) return { success: false, errorCode: "NOT_AUTHENTICATED" }
if (!schoolId) return { success: false, errorCode: "MISSING_SCHOOL_CONTEXT" }
 
// form.tsx
const ERROR_MAP: Record<string, string> = {
  NOT_AUTHENTICATED: dictionary.messages.errors.auth.notAuthenticated,
  MISSING_SCHOOL_CONTEXT:
    dictionary.messages.errors.tenant.missingSchoolContext,
}
toast.error(
  ERROR_MAP[result.errorCode] ?? dictionary.messages.errors.server.internalError
)

Per-area status

All 21 areas (Dashboard, Students, Teachers, Classes, Subjects, Announcements, Events, Assignments, Parents, Grades, Classrooms, Exams, Finance, Attendance, Timetable, Profile, Settings, Notifications, Admission, School Config, Reports) use the dictionary for table headers and sidebar but are partial on getText, forms, selects, validation, toasts, and actions. Students/Teachers/Classes/Subjects/Announcements/Parents/Classrooms/Exams/Attendance/Profile use getText for some surfaces; the rest are display-text-pending.

Wizard routes (/students/add, /teachers/add, /exams/manage, /exams/qbank, /finance/invoice) have hardcoded forms, selects, validation, toasts, and action errors.

Guardrails

LayerWhat it does
.claude/rules/translation.mdPath-scoped rule fires on src/components/** and src/app/** edits
.claude/hooks/check-i18n.shPostToolUse hook flags hardcoded <FormLabel> and toast.success/error strings
CLAUDE.md gotcha #12Reminds that all UI text must use dictionary keys

See also

  • Translation Guide — deep guide to DB-content translation (cache, name helpers, trade-offs, tooling)
  • Internationalization — architecture reference
  • Multi-Tenancy
  • Pattern
  • Validation
  • Form
InternationalizationTranslation Guide

On This Page

ArchitectureInfrastructure filesCoverage gapsPriority orderForm patternSelect patternValidation patternToast patternServer action error patternPer-area statusGuardrailsSee also

Built by Databayt ·

Welcome to balqalam.

A great journey is about to begin.