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 exact — l.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​