- src/i18n/server.ts: createI18nConfig factory consolidating the locale resolution chain (cookie → access_token preferred_language claim → Accept-Language → default). Reusable across apps; previously each frontend reimplemented it. - AdminShell: thread signoutPath + myProfileUrl (default https://my.gosec.internal/profile) into the navbar; render My Profile link alongside logout. - LogoutButton: replace two-step fetch+signOut+redirect with a plain anchor pointing at signoutPath — the NextAuth POST-only signout endpoint plus form-CSRF flow doesn't need client JS. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
74 lines
2.0 KiB
TypeScript
74 lines
2.0 KiB
TypeScript
"use client";
|
|
|
|
import { useTranslations } from "next-intl";
|
|
import type { ChromeLabels } from "./types";
|
|
|
|
/**
|
|
* Default English chrome labels. Used as fallback when next-intl namespace
|
|
* "chrome" doesn't resolve a key and no per-key override is passed in props.
|
|
*/
|
|
export const DEFAULT_CHROME_LABELS: ChromeLabels = {
|
|
navigation: "Navigation",
|
|
main: "MAIN",
|
|
dashboard: "Dashboard",
|
|
support: "Support",
|
|
settings: "Settings",
|
|
allSettings: "All Settings",
|
|
logout: "Logout",
|
|
myProfile: "My Profile",
|
|
docs: "Docs",
|
|
browseApps: "Browse apps",
|
|
viewAll: "View all",
|
|
activityTitle: "Activity",
|
|
newNotifications: "New notifications",
|
|
olderNotifications: "Older notifications",
|
|
noOlderNotifications: "No older notifications",
|
|
noNewNotifications: "No new notifications",
|
|
expandSidebar: "Expand sidebar",
|
|
collapseSidebar: "Collapse sidebar",
|
|
closeSidebar: "Close sidebar",
|
|
};
|
|
|
|
/**
|
|
* Resolve chrome labels with three-level precedence:
|
|
* 1. `overrides` prop (per-key escape hatch)
|
|
* 2. next-intl namespace "chrome" message
|
|
* 3. hardcoded English default
|
|
*
|
|
* Apps add `"chrome": { ... }` to their next-intl messages to translate.
|
|
*/
|
|
export function useChromeLabels(overrides?: Partial<ChromeLabels>): ChromeLabels {
|
|
let t: ((key: string) => string) | null = null;
|
|
try {
|
|
t = useTranslations("chrome");
|
|
} catch {
|
|
t = null;
|
|
}
|
|
|
|
const resolved = { ...DEFAULT_CHROME_LABELS };
|
|
|
|
if (t) {
|
|
for (const key of Object.keys(resolved) as (keyof ChromeLabels)[]) {
|
|
try {
|
|
const v = t(key);
|
|
// next-intl returns the key path when the message is missing — treat
|
|
// that as "no translation" so we fall back to the English default.
|
|
if (v && v !== key && !v.startsWith("chrome.")) {
|
|
resolved[key] = v;
|
|
}
|
|
} catch {
|
|
// Missing key — keep the default.
|
|
}
|
|
}
|
|
}
|
|
|
|
if (overrides) {
|
|
for (const k of Object.keys(overrides) as (keyof ChromeLabels)[]) {
|
|
const v = overrides[k];
|
|
if (v != null) resolved[k] = v;
|
|
}
|
|
}
|
|
|
|
return resolved;
|
|
}
|