The app ships with 11 languages: English, Español, Français, हिन्दी, বাংলা, العربية, اردو, Türkçe, Português, Bahasa Indonesia, and Tiếng Việt. Every user picks their own language independently — it's a per-user preference, not a tenant-wide or installation-wide setting.
Arabic and Urdu are right-to-left languages — the app mirrors the entire layout (dir="rtl") automatically when one of them is active, both on first server-rendered paint and on every client-side navigation.
Changing your own language
Go to Settings → Language and pick from the available options. This takes effect immediately and is remembered for that user on every future visit — no other user's language changes. The platform Landlord/Admin panel has its own equivalent Language page in its top nav, backed by a separate locale column on landlord_users (a distinct table/guard from tenant users — its own preference, independent of any tenant user's).
Behind the scenes, the language a page renders in is resolved in this order: the signed-in user's own saved preference, then the tenant's default (if the user hasn't set one), then the browser's Accept-Language header, then the app's configured default (English). The landlord panel follows the same order minus the tenant-default step (it has no tenant context of its own).
What's translated today
Coverage is comprehensive and spans both the frontend and the backend:
- Every page component under
resources/js/pages— CRM, Sales-to-Cash, Inventory, Accounting, HR, Settings, the landlord panel, onboarding, and auth — renders throughvue-i18n. All 11 locale bundles carry the same 1,870 keys, kept in parity by convention (see below) rather than allowed to drift. - PDF exports (quotes, invoices, credit notes, purchase orders, stock transfers, payslips, and all six accounting reports) render through Laravel's own
__()translator, using the tenant's configured locale for anything a customer receives (an invoice PDF is in the business's language regardless of which staff member's own UI happens to be in a different one) and the acting user's session locale for internal-use downloads. - Notifications and transactional emails (invoice sent, payment recorded, quote accepted, low stock, new lead, leave requests) render in each recipient's own saved locale automatically —
App\Models\Userimplements Laravel'sHasLocalePreferencecontract, so this needs no per-call wiring; Laravel's notification/mail pipeline picks it up on its own.
Two things are their own separate translation surface:
- Laravel framework messages (validation errors like "The name field is required.", pagination labels, and password-reset mail) ship as PHP files under
lang/{locale}/for every supported locale —auth.php,pagination.php,passwords.php, andvalidation.php. Empty-form errors follow the active language the same way the rest of the UI does. - A tenant's own data (item names, party names, notes, and so on) is stored exactly as entered and is never auto-translated. If you need a single item to have a different display name per language, that's a different feature (database-level content translation) — the necessary package (
spatie/laravel-translatable) is already installed but not wired into any model yet.
Adding a new language
- Copy
resources/js/locales/en.jsontoresources/js/locales/{code}.json(e.g.de.jsonfor German) and translate every value — keep every key exactly as-is. Verify parity before shipping: parse both files, flatten their keys through nested objects, and confirm the two key sets are identical (currently 1,870 keys) — a missing key silently falls back to English rather than erroring. - Add the language to
config/app.php'ssupported_localesarray:'supported_locales' => [ 'en' => 'English', 'es' => 'Español', 'fr' => 'Français', 'hi' => 'हिन्दी', 'bn' => 'বাংলা', 'ar' => 'العربية', 'ur' => 'اردو', 'tr' => 'Türkçe', 'pt' => 'Português', 'id' => 'Bahasa Indonesia', 'vi' => 'Tiếng Việt', 'de' => 'Deutsch', // new ], - Add
lang/{code}.jsonfor backend-generated text — flash messages (e.g. "Profile updated."), PDF export labels, and notification/email copy. Copylang/es.jsonas a template (396 keys) and translate every value, keeping every key exactly as-is;:placeholdertokens (Laravel's own syntax, distinct from the{placeholder}syntax in the frontend files from step 1) must be preserved verbatim. No longer just a handful of strings — skipping this file means every PDF export and notification for that language silently falls back to English even though the UI itself is translated. - Copy
lang/en/{auth,pagination,passwords,validation}.phptolang/{code}/and translate those framework messages too — otherwise validation and password-reset emails stay in English. - If the new language is written right-to-left (e.g. Hebrew, Persian), add its code to the
RTL_LOCALESset inresources/js/lib/i18n.tsand to thedircheck inresources/views/app.blade.php— see howar/urare wired for reference. No CSS logical-property rework is needed beyond that; the browser mirrors layout from thedirattribute. - Run
npm run buildso the new bundle is picked up.
The new language now appears automatically on the Language settings page — no other code changes needed. It's loaded lazily (only fetched the first time a user actually selects it), so adding more languages doesn't add weight to everyone else's page loads.
← All Coravo – AI CRM & ERP Software for Growing Businesses documentation