Utilities
The remaining composables round out a frontend layer's toolkit; each section leads with the one to reach for.
i18n
useTranslation — the $-prefix convention
Backend-driven screens pass strings that may be either a literal ("Save") or a translation key. The DMS disambiguates with a leading $: a value beginning with $ is an i18n key, anything else is shown verbatim. processI18n applies that rule:
const { processI18n } = useTranslation();
processI18n("Save"); // → "Save" (literal, returned as-is)
processI18n("$actions.save"); // → t("actions.save") — the leading $ is stripped
processI18n("$items.count", { n: 3 }); // params forwarded to t()
A custom builder can therefore accept user-authored labels and translation keys through the same prop — only the $-prefixed ones get looked up. Define the matching keys in your layer's locale files (Localization).
Outside component context — a Nuxt plugin, where useI18n() is unavailable — use the plugin-safe pair instead: getPluginI18n() returns the global composer plus a bound translate, and resolveI18nKey(translate, key, params?) applies the same $-prefix rule. This is how command-palette sources translate backend display names:
const { i18n, translate } = getPluginI18n();
resolveI18nKey(translate, "$actions.save"); // → t("actions.save")
resolveI18nKey(translate, "Save"); // → "Save"
useUniqueLocales — for language switchers
useI18n().locales can list the same language code more than once. useUniqueLocales returns a computed deduplicated by code (first occurrence wins) — use it to populate a locale picker.
const { uniqueLocales } = useUniqueLocales();
// uniqueLocales.value → one entry per code
Leave guard
usePageLeaveGuard — block navigation from a dirty form
The ergonomic entry point. It is page-scoped: guards are keyed by the current route and cleared automatically on unmount, so you register and forget.
registerGuard(callback) returns an unregister function. A callback that returns false (or throws) blocks navigation; returning anything else allows it. Callbacks may be async — guards are awaited.
const { registerGuard } = usePageLeaveGuard();
const { confirm } = useConfirm();
const isDirty = ref(false);
onMounted(() => {
registerGuard(async () => {
if (!isDirty.value) return true; // clean → allow
return confirm({
title: "Unsaved changes",
description: "Discard them and leave?",
}); // false → stay
});
});
The dialog comes from useConfirm (UI toolkit), so the prompt matches the rest of the dashboard.
useLeaveGuard — the multi-guard primitive (advanced)
usePageLeaveGuard is a thin wrapper over useLeaveGuard, the lower-level registry. Reach for it only when guards must be scoped to something other than the current page — a multi-step container that outlives the route, for example. It exposes:
addGuard(containerId, callback)→ returns an unregister function.removeGuard(containerId, guardId)— remove a single guard.executeGuards(containerId)→Promise<boolean>— runs the container's guards in registration order;falseif any returnsfalseor throws.clearGuards(containerId)— drop all guards for a container.
You own the containerId namespace and the cleanup.
Onboarding
useOnboarding — read onboarding state
useOnboarding() is async and resolves to a hasOnboarded flag. The first call fetches the state from the backend and caches it in shared state; subsequent calls return immediately.
const { hasOnboarded } = await useOnboarding();
setOnboardingComplete — release the guard without a reload
The backend flips the onboarding flag when the first admin registers, but the cached client value stays stale until a full reload. Call setOnboardingComplete() to set the cached flag to true, so the global onboarding guard lets the user leave the wizard immediately — wire it to a wizard "finish" button.
async function finish() {
// ...persist the wizard's result via your own backend route ($authFetch)...
setOnboardingComplete(); // cached flag → true; no reload needed
await navigateTo("/");
}
/onboarding, and the wizard keeps already-onboarded users out — is built-in route middleware; you configure nothing for it.