diff --git a/.changeset/sunny-titles-resolve.md b/.changeset/sunny-titles-resolve.md
new file mode 100644
index 000000000..315d1416c
--- /dev/null
+++ b/.changeset/sunny-titles-resolve.md
@@ -0,0 +1,5 @@
+---
+"@tailor-platform/app-shell": patch
+---
+
+Fix `SidebarItem` title overrides accepting localized strings. Pass a value from `defineI18nLabels` directly to `title` and it resolves with the active AppShell locale.
diff --git a/docs-manifest.json b/docs-manifest.json
index 718997a22..70509a247 100644
--- a/docs-manifest.json
+++ b/docs-manifest.json
@@ -1147,10 +1147,10 @@
"claims": [],
"symbols": ["SidebarItem", "SidebarItemProps"],
"hashes": {
- "typeSurface": "7be21739c231ede3",
- "outline": "1cc674b956d09fab",
+ "typeSurface": "37530cafa374d799",
+ "outline": "d1bbcfac50071047",
"snapshot": null,
- "outputMd": "d55d38352040ef2c",
+ "outputMd": "9236c03e53aa79f2",
"examples": null
}
},
@@ -1182,9 +1182,9 @@
"symbols": [],
"hashes": {
"typeSurface": null,
- "outline": "74f353988f15c82b",
+ "outline": "91f0fa065f3a3192",
"snapshot": null,
- "outputMd": "96f1cf893c231861",
+ "outputMd": "dc6b3afc049b4148",
"examples": null
}
},
@@ -1587,7 +1587,7 @@
"packages/core/skills/app-shell-patterns/references/concepts/graphql.md": "ba5107604e4f42e8",
"packages/core/skills/app-shell-patterns/references/concepts/modules-and-resources.md": "bf15b5460f22d165",
"packages/core/skills/app-shell-patterns/references/concepts/routing-navigation.md": "2f3ff3f095c2b1cc",
- "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "96f1cf893c231861",
+ "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "dc6b3afc049b4148",
"packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "0133fdae405f467c",
"packages/core/skills/app-shell-patterns/references/components/action-panel.md": "71794d4b561e4d4b",
"packages/core/skills/app-shell-patterns/references/components/activity-card.md": "4f386e49380fb0b0",
@@ -1622,7 +1622,7 @@
"packages/core/skills/app-shell-patterns/references/components/select.md": "ae287e2bb5abc1c6",
"packages/core/skills/app-shell-patterns/references/components/sheet.md": "1f81ebd562c50371",
"packages/core/skills/app-shell-patterns/references/components/sidebar-group.md": "7288fdf8bb5c19b9",
- "packages/core/skills/app-shell-patterns/references/components/sidebar-item.md": "d55d38352040ef2c",
+ "packages/core/skills/app-shell-patterns/references/components/sidebar-item.md": "9236c03e53aa79f2",
"packages/core/skills/app-shell-patterns/references/components/sidebar-layout.md": "e50b2d649289d772",
"packages/core/skills/app-shell-patterns/references/components/spinner.md": "e05d94ba9c3d17da",
"packages/core/skills/app-shell-patterns/references/components/table.md": "f85ec64ceb7f4acf",
diff --git a/docs-src/components/sidebar-item.docs.outline.md b/docs-src/components/sidebar-item.docs.outline.md
index 3abc60271..9823d083d 100644
--- a/docs-src/components/sidebar-item.docs.outline.md
+++ b/docs-src/components/sidebar-item.docs.outline.md
@@ -9,7 +9,7 @@ sources:
# SidebarItem
-`SidebarItem` represents an individual navigation link in the sidebar. It automatically resolves titles and icons from resource definitions and provides active state highlighting.
+`SidebarItem` represents an individual navigation link in the sidebar. It automatically resolves titles and icons from resource definitions, supports localized title overrides, and provides active state highlighting.
## Import
@@ -36,7 +36,7 @@ When you only provide `to`, the title and icon are automatically resolved from t
| Prop | Type | Default | Description |
| ------------- | ---------------------- | ------------- | ---------------------------------------------- |
| `to` | `string` | **Required** | Target URL (internal or external) |
-| `title` | `string` | Auto-resolved | Override the display title |
+| `title` | `LocalizedString` | Auto-resolved | Override the display title (i18n supported) |
| `icon` | `React.ReactNode` | Auto-resolved | Override the icon |
| `external` | `boolean` | `false` | Opens link in new tab with external icon |
| `activeMatch` | `"exact" \| "prefix"` | `"prefix"` | How to match the current path for active state |
@@ -106,6 +106,14 @@ const modules = [
/>
```
+### Localized Override
+
+Pass a `LocalizedString`, such as a value from `defineI18nLabels`, to resolve the override using the current AppShell locale:
+
+```tsx
+
+```
+
### Override Icon
```tsx
diff --git a/docs-src/concepts/sidebar-navigation.docs.outline.md b/docs-src/concepts/sidebar-navigation.docs.outline.md
index c8020c22d..853d9eace 100644
--- a/docs-src/concepts/sidebar-navigation.docs.outline.md
+++ b/docs-src/concepts/sidebar-navigation.docs.outline.md
@@ -72,13 +72,13 @@ A navigation item that automatically resolves title and icon from resource defin
#### Props
-| Prop | Type | Required | Description |
-| ---------- | ----------- | -------- | --------------------------------------------------------------------- |
-| `to` | `string` | Yes | Target URL. External URLs (http://...) are rendered as external links |
-| `title` | `string` | No | Override title. When omitted, auto-resolved from resource meta |
-| `icon` | `ReactNode` | No | Override icon. When omitted, auto-resolved from resource meta |
-| `external` | `boolean` | No | Opens link in new tab with external link icon |
-| `render` | `function` | No | Custom render function for full UI control |
+| Prop | Type | Required | Description |
+| ---------- | ----------------- | -------- | ------------------------------------------------------------------------------- |
+| `to` | `string` | Yes | Target URL. External URLs (http://...) are rendered as external links |
+| `title` | `LocalizedString` | No | Override title (i18n supported). When omitted, auto-resolved from resource meta |
+| `icon` | `ReactNode` | No | Override icon. When omitted, auto-resolved from resource meta |
+| `external` | `boolean` | No | Opens link in new tab with external link icon |
+| `render` | `function` | No | Custom render function for full UI control |
#### Examples
diff --git a/docs/components/sidebar-item.md b/docs/components/sidebar-item.md
index be600e416..160b757b1 100644
--- a/docs/components/sidebar-item.md
+++ b/docs/components/sidebar-item.md
@@ -7,7 +7,7 @@ description: Individual navigation item for customizing sidebar with icons, acti
# SidebarItem
-`SidebarItem` represents an individual navigation link in the sidebar. It automatically resolves titles and icons from resource definitions and provides active state highlighting.
+`SidebarItem` represents an individual navigation link in the sidebar. It automatically resolves titles and icons from resource definitions, supports localized title overrides, and provides active state highlighting.
## Import
@@ -34,7 +34,7 @@ When you only provide `to`, the title and icon are automatically resolved from t
| Prop | Type | Default | Description |
| ------------- | ---------------------- | ------------- | ---------------------------------------------- |
| `to` | `string` | **Required** | Target URL (internal or external) |
-| `title` | `string` | Auto-resolved | Override the display title |
+| `title` | `LocalizedString` | Auto-resolved | Override the display title (i18n supported) |
| `icon` | `React.ReactNode` | Auto-resolved | Override the icon |
| `external` | `boolean` | `false` | Opens link in new tab with external icon |
| `activeMatch` | `"exact" \| "prefix"` | `"prefix"` | How to match the current path for active state |
@@ -104,6 +104,14 @@ const modules = [
/>
```
+### Localized Override
+
+Pass a `LocalizedString`, such as a value from `defineI18nLabels`, to resolve the override using the current AppShell locale:
+
+```tsx
+
+```
+
### Override Icon
```tsx
diff --git a/docs/concepts/sidebar-navigation.md b/docs/concepts/sidebar-navigation.md
index 843f937fb..4c3924431 100644
--- a/docs/concepts/sidebar-navigation.md
+++ b/docs/concepts/sidebar-navigation.md
@@ -72,13 +72,13 @@ A navigation item that automatically resolves title and icon from resource defin
#### Props
-| Prop | Type | Required | Description |
-| ---------- | ----------- | -------- | --------------------------------------------------------------------- |
-| `to` | `string` | Yes | Target URL. External URLs (http://...) are rendered as external links |
-| `title` | `string` | No | Override title. When omitted, auto-resolved from resource meta |
-| `icon` | `ReactNode` | No | Override icon. When omitted, auto-resolved from resource meta |
-| `external` | `boolean` | No | Opens link in new tab with external link icon |
-| `render` | `function` | No | Custom render function for full UI control |
+| Prop | Type | Required | Description |
+| ---------- | ----------------- | -------- | ------------------------------------------------------------------------------- |
+| `to` | `string` | Yes | Target URL. External URLs (http://...) are rendered as external links |
+| `title` | `LocalizedString` | No | Override title (i18n supported). When omitted, auto-resolved from resource meta |
+| `icon` | `ReactNode` | No | Override icon. When omitted, auto-resolved from resource meta |
+| `external` | `boolean` | No | Opens link in new tab with external link icon |
+| `render` | `function` | No | Custom render function for full UI control |
#### Examples
diff --git a/packages/core/src/components/sidebar/sidebar-item.tsx b/packages/core/src/components/sidebar/sidebar-item.tsx
index f8edaf9a5..2fb641b29 100644
--- a/packages/core/src/components/sidebar/sidebar-item.tsx
+++ b/packages/core/src/components/sidebar/sidebar-item.tsx
@@ -3,6 +3,8 @@ import { useLocation, Link } from "react-router";
import { ExternalLink } from "lucide-react";
import { SidebarMenuItem, SidebarMenuButton } from "./primitives";
import { usePageMeta } from "@/hooks/use-page-meta";
+import { useAppShellConfig } from "@/contexts/appshell-context";
+import { buildLocaleResolver, type LocalizedString } from "@/lib/i18n";
export type SidebarItemRenderProps = {
/** Title auto-resolved from resource meta */
@@ -23,10 +25,10 @@ export type SidebarItemProps = {
to: string;
/**
- * Override title.
+ * Override title (i18n supported).
* When omitted, title is auto-resolved from resource meta.
*/
- title?: string;
+ title?: LocalizedString;
/**
* Override icon.
@@ -96,13 +98,19 @@ export const SidebarItem = (props: SidebarItemProps) => {
icon: iconOverride,
} = props;
const { pathname: currentPath } = useLocation();
+ const { configurations } = useAppShellConfig();
const pageMeta = usePageMeta(to);
+ const resolve = buildLocaleResolver(configurations.locale);
const isActive =
activeMatch === "exact"
? currentPath === to
: currentPath === to || currentPath.startsWith(`${to}/`);
- const title = titleOverride ?? pageMeta?.title ?? extractTitleFromUrl(to);
+ const fallbackTitle = pageMeta?.title ?? extractTitleFromUrl(to);
+ const title =
+ typeof titleOverride === "function"
+ ? resolve(titleOverride, fallbackTitle)
+ : (titleOverride ?? fallbackTitle);
const icon = iconOverride ?? pageMeta?.icon;
const renderProps: SidebarItemRenderProps = {
diff --git a/packages/core/src/components/sidebar/sidebar.test.tsx b/packages/core/src/components/sidebar/sidebar.test.tsx
index bb495e437..b0d5f6a60 100644
--- a/packages/core/src/components/sidebar/sidebar.test.tsx
+++ b/packages/core/src/components/sidebar/sidebar.test.tsx
@@ -90,11 +90,15 @@ const testConfig: RootConfiguration = {
/**
* Wrapper to render sidebar components with all required providers.
*/
-const renderWithProviders = (ui: React.ReactNode, initialPath = "/dashboard/overview") => {
+const renderWithProviders = (
+ ui: React.ReactNode,
+ initialPath = "/dashboard/overview",
+ locale = "en",
+) => {
stubDesktopViewport();
return render(
-
+
{ui}
@@ -118,6 +122,18 @@ describe("SidebarItem", () => {
expect(menu.getByRole("link", { name: /dashboard/i })).toBeDefined();
});
+ it("resolves localized title overrides", () => {
+ renderWithProviders(, undefined, "ja");
+
+ expect(getSidebarMenu().getByRole("link", { name: "製品" })).toBeDefined();
+ });
+
+ it("preserves empty string title overrides", () => {
+ renderWithProviders();
+
+ expect(getSidebarMenu().getByRole("link").textContent).toBe("");
+ });
+
it("renders with custom render function", () => {
renderWithProviders(