Skip to main content

Translations and icons

Two cosmetic but high-impact fields on every menu item:

FieldWhat it does
lMap of per-language label overrides — l.fr, l.de, l.es. The framework picks the one matching the user's language; the default label is the fallback.
iconA Lucide icon name (shield, users, chart-bar, …). Renders at the start of the row in the sidebar.

This page covers both — when to set them, the picker conventions, the runtime resolution rules.


Translations — the l map

The label field is the default language label. The l map adds per-language overrides:

[[menus.crm.items]]
id = "security"
label = "Security"
l.fr = "Sécurité"
l.de = "Sicherheit"
l.es = "Seguridad"
icon = "shield"

When a French user opens the app, the sidebar renders Sécurité. A German user sees Sicherheit. A user whose language doesn't match any l.* entry falls back to Security.

Resolution rules

At runtime, for each menu item:

  1. Read the user's language from the JWT (or the browser's Accept-Language header).
  2. Look up l[<language>].
  3. If present and non-empty, use that string.
  4. Otherwise, use the default label.

The lookup is exactl.fr is found for users whose language is fr; a user with fr-CA won't match l.fr unless you add l["fr-CA"] too. For maximum coverage, set the bare language code (fr, de, es); locale-specific entries (fr-CA, de-AT) are reserved for genuine variants.

The Translations tab in the Inspector

The Inspector's Translations tab groups every l.* entry — adding a new language picks from SUPPORTED_LANGUAGES (the framework's enabled-languages list from app.toml). Removing a language drops the entry.

Field shownWhat
Default labelThe label field — fallback for any user whose language has no override.
Per-language rowsOne per l.<lang> entry. Empty values delete the entry on save.

Translating folders

Folders need translations too — the user sees the folder title in the sidebar. Same l shape applies. Don't translate the id (it's the technical key, never shown).

Translating leaves

For query leaves, the leaf's label is the menu label. The opened screen's title comes from the screen's label / description, which has its own translations through the dictionary. Two layers:

LayerWhere translated
Menu labell on the menu item.
Screen title[dictionary.<id>] entries — language-aware.
Column labelsSame — [dictionary.<col_id>] for each column.

The dictionary system covers screen-side text once; the menu's l covers the navigation labels.

When NOT to translate

PatternWhy
Internal-only apps with one language.Save the effort; just use label.
Apps where the audience all speaks the same business language.Same.
The label is a code or identifier the user reads as-is.F0005, IBAN, SLA — leave them as-is.

Icons — Lucide names

The icon field accepts any Lucide icon name. The icon renders to the left of the label in the sidebar, sized to match the row height.

[[menus.crm.items]]
id = "pipeline"
label = "Pipeline"
icon = "briefcase"

Renders a briefcase icon next to the Pipeline folder.

How to pick a name

  • Browse lucide.dev — every icon has its name visible.
  • Names are lowercase kebab-case: chart-bar, git-branch, arrow-up-right.
  • The framework matches case-sensitively — typos render no icon (no error).

Conventions

A consistent icon vocabulary makes the menu legible at a glance. The patterns most installs settle on:

ConceptIcon
Users / peopleusers, user, user-check
Security / permissionsshield, key, lock
Reports / chartschart-bar, chart-line, chart-pie, trending-up
Tables / datatable, database, layers
Settings / configsettings, cog, sliders-horizontal
Sync / refreshrefresh-cw, repeat, cloud-download
Pipeline / workflowbriefcase, workflow, git-branch
Notificationsbell, mail, inbox
Audit / historyhistory, clock, file-clock
Calendarcalendar, calendar-clock
Documentsfile, file-text, folder

Pick one per domain and stay with it across the app. Switching between Users (users) and Roles (shield) within a Security folder reads consistently; mixing icons at random doesn't.

Folders vs leaves

Item kindIcon convention
FolderUsually an "area" icon (shield for Security, briefcase for Pipeline). Renders next to the chevron.
LeafUsually a "thing" icon (users for the Users screen, file-text for Documents).

If you skip icon on a folder, the framework renders a default folder glyph. Leaves without icon render with a default file glyph.

When to skip icons

PatternWhy
The label is short and unambiguous.The icon doesn't add information — just visual noise.
You're prototyping.Add icons last, after the structure stabilises.
The menu is short (5 items or fewer).Icons don't help scanability when there's little to scan.

A consistent icon set helps with 30+ item menus; below that, labels alone read fine.


A concrete example — same menu, three languages

The Nomasx-1 app's Security folder, fully translated:

[[menus.nomasx1.items]]
id = "security"
label = "Security"
l.fr = "Sécurité"
l.de = "Sicherheit"
icon = "shield"

[[menus.nomasx1.items]]
id = "security.users"
parent = "security"
label = "Users"
l.fr = "Utilisateurs"
l.de = "Benutzer"
icon = "users"
type = "query"
target = "security_users_get"

[[menus.nomasx1.items]]
id = "security.roles"
parent = "security"
label = "Roles"
l.fr = "Rôles"
l.de = "Rollen"
icon = "key"
type = "query"
target = "security_roles_get"

[[menus.nomasx1.items]]
id = "security.assignments"
parent = "security"
label = "User-role assignments"
l.fr = "Attributions utilisateur ↔ rôle"
l.de = "Benutzer-Rollen-Zuordnungen"
icon = "git-branch"
type = "query"
target = "security_assignments_get"

A French user sees Sécurité ▾ → Utilisateurs / Rôles / Attributions utilisateur ↔ rôle. A German user sees Sicherheit ▾ → Benutzer / Rollen / Benutzer-Rollen-Zuordnungen. An English user sees the defaults. All three use the same icons.


Common pitfalls

MistakeSymptomFix
l.FR (uppercase).French users see the default English label — the lookup is case-sensitive.Use lowercase language codes.
icon = "Shield" (capitalised).No icon renders (no error).Use lowercase Lucide names.
Icon name from another library (FontAwesome, Material).No icon renders.Pick from lucide.dev.
Translations only on the menu, not on screens / dictionary.Sidebar shows Utilisateurs but the screen title is Users.Translate at both layers — menu's l AND the dictionary's [dictionary.<id>].l_<lang>.
l.fr-CA set; French Canadian user falls back to English.The exact-match resolution means fr-CA doesn't fall through to fr.Either add l.fr as a fallback (the regional variant overrides it for fr-CA users) or simplify to l.fr.

What's next